Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ jobs:
(cd examples && mm.py ./src/main/java/io/dapr/examples/crypto/README.md)
(cd examples && mm.py ./src/main/java/io/dapr/examples/binarystore/README.md)
(cd examples && mm.py ./src/main/java/io/dapr/examples/vector/README.md)
(cd examples && mm.py ./src/main/java/io/dapr/examples/search/README.md)
(cd examples && mm.py ./src/main/java/io/dapr/examples/workflows/README.md)
(cd spring-boot-examples/pubsub && mm.py README.md)
(cd spring-boot-examples/workflows/patterns && mm.py README.md)
Expand Down
7 changes: 7 additions & 0 deletions examples/components/search/inmemory.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: searchstore
spec:
type: search.in-memory
version: v1
149 changes: 149 additions & 0 deletions examples/src/main/java/io/dapr/examples/search/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
## Dapr Search API Example (alpha)

This example shows how to create a search index, store documents, search them, and delete them with the Dapr search API. The search API is an alpha API in Dapr 1.19. Its names and behavior can change in a later release.

### Using the Search API

The Java SDK has these methods on `DaprPreviewClient`:
* `client.createSearchIndex(...)`, `client.getSearchIndex(...)`, `client.listSearchIndexes(...)` and `client.deleteSearchIndex(...)` manage indexes.
* `client.indexSearchDocuments(...)` stores documents by ID, and `client.getSearchDocuments(...)` and `client.deleteSearchDocuments(...)` read and delete them.
* `client.search(...)` finds documents by full-text query, by a query in the store's own format, or by filter alone, and returns the hits one page at a time.

## Pre-requisites

* [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) and Dapr runtime 1.19 or later.
* Java JDK 17 (or greater):
* [Microsoft JDK 17](https://docs.microsoft.com/en-us/java/openjdk/download#openjdk-17)
* [Oracle JDK 17](https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK17)
* [OpenJDK 17](https://jdk.java.net/17/)
* [Apache Maven](https://maven.apache.org/install.html) version 3.x.

### Checking out the code

Clone this repository:

```sh
git clone https://github.com/dapr/java-sdk.git
cd java-sdk
```

Then build the Maven project:

```sh
# make sure you are in the `java-sdk` directory.
mvn install
```

Then get into the examples directory:

```sh
cd examples
```

### Initialize Dapr

Run `dapr init` to initialize Dapr in Self-Hosted Mode if it's not already initialized.

### The search store component

The example uses the in-memory search store component in `./components/search/inmemory.yaml`:

```yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: searchstore
spec:
type: search.in-memory
version: v1
```

The in-memory store keeps documents only in the sidecar's memory. Use a different search store component, such as `search.meilisearch`, to keep them.

### Running the Example

`SearchExample.java` creates a `books` index, stores three books, and then searches them. A document's content is a UTF-8 encoded JSON object:

```java
try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) {
client.createSearchIndex("searchstore", "books").block();

List<SearchDocument> books = List.of(
new SearchDocument("dune",
"{\"title\":\"Dune\",\"genre\":\"scifi\",\"year\":1965}".getBytes(StandardCharsets.UTF_8)),
...);
client.indexSearchDocuments(new IndexSearchDocumentsRequestAlpha1("searchstore", "books", books)
.setOptions(IndexingOptions.waitForCompletion(Duration.ofSeconds(5), IndexingWaitTimeoutAction.FAIL_REQUEST)))
.block();

SearchResponseAlpha1 text = client.search(
new SearchRequestAlpha1("searchstore", "books", SearchQuery.byText("hobbit"))).block();

SearchResponseAlpha1 scifi = client.search(new SearchRequestAlpha1("searchstore", "books",
SearchQuery.matchAll()
.setFilter(Map.of("genre", Map.of("$eq", "scifi")))
.setSort(List.of(SortClause.asc("year"))))).block();
}
```

The write waits until the store has indexed the documents, so the searches that follow see them. Without indexing options, a write returns as soon as the store accepts it.

The example also reads every book in pages of two: each response has a continuation token until the last page, and the next request sends the same query with that token.

Use the following command to run this example:

<!-- STEP
name: Run Search Example
expected_stdout_lines:
- "Created index: books"
- "Stored books: 3, COMPLETED, failed: 0"
- "Index books: 3 documents"
- "Full-text search: [hobbit]"
- "Science fiction, oldest first: [foundation, dune]"
- "Page 1 of 3 books: [dune, foundation]"
- "Page 2 of 3 books: [hobbit]"
- "Read dune: {\"title\":\"Dune\",\"genre\":\"scifi\",\"year\":1965}"
- "Deleted document dune: COMPLETED"
- "Deleted index: books"
- "SUCCESS: The search example finished."
background: true
output_match_mode: substring
sleep: 30
-->

```bash
dapr run --resources-path ./components/search --app-id search-app --dapr-http-port 3500 --dapr-grpc-port 50001 -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.search.SearchExample
```

<!-- END_STEP -->

### Sample Output

```
=== Dapr Search Example ===
Created index: books
Stored books: 3, COMPLETED, failed: 0
Index books: 3 documents
Full-text search: [hobbit]
Science fiction, oldest first: [foundation, dune]
Page 1 of 3 books: [dune, foundation]
Page 2 of 3 books: [hobbit]
Read dune: {"title":"Dune","genre":"scifi","year":1965}
Deleted document dune: COMPLETED
Deleted index: books
SUCCESS: The search example finished.
```

### Cleanup

To stop the app, run (or press CTRL+C):

<!-- STEP
name: Cleanup
-->

```bash
dapr stop --app-id search-app
```

<!-- END_STEP -->
132 changes: 132 additions & 0 deletions examples/src/main/java/io/dapr/examples/search/SearchExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright 2026 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/

package io.dapr.examples.search;

import io.dapr.client.DaprClientBuilder;
import io.dapr.client.DaprPreviewClient;
import io.dapr.client.domain.DeleteSearchDocumentsRequestAlpha1;
import io.dapr.client.domain.GetSearchDocumentsRequestAlpha1;
import io.dapr.client.domain.GetSearchIndexResponseAlpha1;
import io.dapr.client.domain.IndexAck;
import io.dapr.client.domain.IndexSearchDocumentsRequestAlpha1;
import io.dapr.client.domain.IndexSearchDocumentsResponseAlpha1;
import io.dapr.client.domain.IndexingOptions;
import io.dapr.client.domain.IndexingWaitTimeoutAction;
import io.dapr.client.domain.SearchDocument;
import io.dapr.client.domain.SearchHit;
import io.dapr.client.domain.SearchQuery;
import io.dapr.client.domain.SearchRequestAlpha1;
import io.dapr.client.domain.SearchResponseAlpha1;
import io.dapr.client.domain.SortClause;
import io.dapr.config.Properties;
import io.dapr.config.Property;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* Stores, reads, searches, and deletes documents with the Dapr search API (alpha).
*
* <p>Run with the in-memory search store component in components/search.
*/
public class SearchExample {

private static final String STORE_NAME = "searchstore";
private static final String INDEX = "books";

/**
* The main method of the example.
*
* @param args Command line arguments (unused).
* @throws Exception if the client fails to close.
*/
public static void main(String[] args) throws Exception {
Map<Property<?>, String> overrides = Map.of(
Properties.HTTP_PORT, "3500",
Properties.GRPC_PORT, "50001"
);

try (DaprPreviewClient client = new DaprClientBuilder().withPropertyOverrides(overrides).buildPreviewClient()) {
System.out.println("=== Dapr Search Example ===");

client.createSearchIndex(STORE_NAME, INDEX).block();
System.out.println("Created index: " + INDEX);

List<SearchDocument> books = List.of(
book("dune", "{\"title\":\"Dune\",\"genre\":\"scifi\",\"year\":1965}"),
book("foundation", "{\"title\":\"Foundation\",\"genre\":\"scifi\",\"year\":1951}"),
book("hobbit", "{\"title\":\"The Hobbit\",\"genre\":\"fantasy\",\"year\":1937}"));
IndexSearchDocumentsResponseAlpha1 indexed = client.indexSearchDocuments(
new IndexSearchDocumentsRequestAlpha1(STORE_NAME, INDEX, books)
.setOptions(IndexingOptions.waitForCompletion(
Duration.ofSeconds(5), IndexingWaitTimeoutAction.FAIL_REQUEST))).block();
System.out.println("Stored books: " + books.size() + ", " + indexed.getAck()
+ ", failed: " + indexed.getFailedItems().size());

GetSearchIndexResponseAlpha1 index = client.getSearchIndex(STORE_NAME, INDEX).block();
System.out.println("Index " + index.getIndex() + ": " + index.getDocumentCount() + " documents");

SearchResponseAlpha1 text = client.search(
new SearchRequestAlpha1(STORE_NAME, INDEX, SearchQuery.byText("hobbit"))).block();
System.out.println("Full-text search: " + ids(text));

SearchResponseAlpha1 scifi = client.search(new SearchRequestAlpha1(STORE_NAME, INDEX,
SearchQuery.matchAll()
.setFilter(Map.of("genre", Map.of("$eq", "scifi")))
.setSort(List.of(SortClause.asc("year"))))).block();
System.out.println("Science fiction, oldest first: " + ids(scifi));

// A continuation token is bound to its query, so each page sends the same query with the new token.
String token = null;
int page = 1;
do {
SearchResponseAlpha1 response = client.search(new SearchRequestAlpha1(STORE_NAME, INDEX,
SearchQuery.matchAll().setTopK(2).setContinuationToken(token))).block();
System.out.println("Page " + page + " of " + response.getTotalHits() + " books: " + ids(response));
token = response.getContinuationToken();
page++;
} while (token != null);

List<SearchDocument> found = client.getSearchDocuments(
new GetSearchDocumentsRequestAlpha1(STORE_NAME, INDEX, List.of("dune", "missing"))
.setIncludeContent(true)).block();
for (SearchDocument document : found) {
String content = new String(document.getContent(), StandardCharsets.UTF_8);
System.out.println("Read " + document.getId() + ": " + content);
}

IndexAck deleted = client.deleteSearchDocuments(
new DeleteSearchDocumentsRequestAlpha1(STORE_NAME, INDEX, List.of("dune"))).block();
System.out.println("Deleted document dune: " + deleted);

client.deleteSearchIndex(STORE_NAME, INDEX).block();
System.out.println("Deleted index: " + INDEX);
System.out.println("SUCCESS: The search example finished.");
}
}

private static SearchDocument book(String id, String json) {
return new SearchDocument(id, json.getBytes(StandardCharsets.UTF_8));
}

private static List<String> ids(SearchResponseAlpha1 response) {
return response.getHits().stream()
.map(SearchHit::getDocument)
.map(SearchDocument::getId)
.collect(Collectors.toList());
}
}
Loading
Loading