Skip to content
Merged
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 @@ -123,6 +123,7 @@ jobs:
set -e
(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/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/vector/inmemory.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: vectorstore
spec:
type: vector.in-memory
version: v1
149 changes: 149 additions & 0 deletions examples/src/main/java/io/dapr/examples/vector/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
## Dapr Vector API Example (alpha)

This example shows how to create a vector collection, store vectors, query them by similarity, and delete them with the Dapr vector API. The vector API is an alpha API in Dapr 1.19. Its names and behavior can change in a later release.

### Using the Vector API

The Java SDK has these methods on `DaprPreviewClient`:
* `client.createVectorCollection(...)`, `client.getVectorCollection(...)`, `client.listVectorCollections(...)` and `client.deleteVectorCollection(...)` manage collections.
* `client.upsertVectors(...)` stores records by ID, and `client.getVectors(...)` and `client.deleteVectors(...)` read and delete them.
* `client.queryVectors(...)` finds the records closest to a vector or to a stored record, optionally filtered on metadata.
* `client.batchQueryVectors(...)` runs several queries in one call. Each query succeeds or fails on its own.

## 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 vector store component

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

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

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

### Running the Example

`VectorExample.java` creates a `books` collection with 3 dimensions and the cosine metric, stores three books, and then queries them:

```java
try (DaprPreviewClient client = new DaprClientBuilder().buildPreviewClient()) {
client.createVectorCollection(new CreateVectorCollectionRequestAlpha1("vectorstore", "books", 3)
.setMetric(DistanceMetric.COSINE)).block();

List<VectorRecord> books = List.of(
new VectorRecord("dune", new float[] {0.9f, 0.1f, 0.0f}).setMetadata(Map.of("genre", "scifi")),
...);
client.upsertVectors(new UpsertVectorsRequestAlpha1("vectorstore", "books", books)
.setOptions(IndexingOptions.waitForCompletion(Duration.ofSeconds(5), IndexingWaitTimeoutAction.FAIL_REQUEST)))
.block();

QueryVectorsResponseAlpha1 closest = client.queryVectors(new QueryVectorsRequestAlpha1("vectorstore", "books",
VectorQuery.byVector(new float[] {0.85f, 0.15f, 0.05f}).setTopK(2))).block();

QueryVectorsResponseAlpha1 fantasy = client.queryVectors(new QueryVectorsRequestAlpha1("vectorstore", "books",
VectorQuery.byVector(new float[] {0.85f, 0.15f, 0.05f})
.setFilter(Map.of("genre", Map.of("$eq", "fantasy"))))).block();
}
```

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

The example also runs a batch of two queries, one of which asks for a record that does not exist. That query fails with `NOT_FOUND` on its own, and the other query still returns its matches.

Use the following command to run this example:

<!-- STEP
name: Run Vector Example
expected_stdout_lines:
- "Created collection: books"
- "Stored books: 3, COMPLETED, failed: 0"
- "Collection books: 3 dimensions, COSINE"
- "Closest two books: [dune, foundation]"
- "Closest fantasy book: [hobbit]"
- "Batch query 0: [hobbit]"
- "Batch query 1 failed: NOT_FOUND"
- "Read records: [dune]"
- "Deleted record dune: COMPLETED"
- "Deleted collection: books"
- "SUCCESS: The vector example finished."
background: true
output_match_mode: substring
sleep: 30
-->

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

<!-- END_STEP -->

### Sample Output

```
=== Dapr Vector Example ===
Created collection: books
Stored books: 3, COMPLETED, failed: 0
Collection books: 3 dimensions, COSINE
Closest two books: [dune, foundation]
Closest fantasy book: [hobbit]
Batch query 0: [hobbit]
Batch query 1 failed: NOT_FOUND
Read records: [dune]
Deleted record dune: COMPLETED
Deleted collection: books
SUCCESS: The vector example finished.
```

### Cleanup

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

<!-- STEP
name: Cleanup
-->

```bash
dapr stop --app-id vector-app
```

<!-- END_STEP -->
132 changes: 132 additions & 0 deletions examples/src/main/java/io/dapr/examples/vector/VectorExample.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.vector;

import io.dapr.client.DaprClientBuilder;
import io.dapr.client.DaprPreviewClient;
import io.dapr.client.domain.BatchQueryVectorsRequestAlpha1;
import io.dapr.client.domain.BatchQueryVectorsResultAlpha1;
import io.dapr.client.domain.CreateVectorCollectionRequestAlpha1;
import io.dapr.client.domain.DeleteVectorsRequestAlpha1;
import io.dapr.client.domain.DistanceMetric;
import io.dapr.client.domain.GetVectorCollectionResponseAlpha1;
import io.dapr.client.domain.GetVectorsRequestAlpha1;
import io.dapr.client.domain.IndexAck;
import io.dapr.client.domain.IndexingOptions;
import io.dapr.client.domain.IndexingWaitTimeoutAction;
import io.dapr.client.domain.QueryVectorsRequestAlpha1;
import io.dapr.client.domain.QueryVectorsResponseAlpha1;
import io.dapr.client.domain.UpsertVectorsRequestAlpha1;
import io.dapr.client.domain.UpsertVectorsResponseAlpha1;
import io.dapr.client.domain.VectorMatch;
import io.dapr.client.domain.VectorQuery;
import io.dapr.client.domain.VectorRecord;
import io.dapr.config.Properties;
import io.dapr.config.Property;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* Stores, reads, queries, and deletes vectors with the Dapr vector API (alpha).
*
* <p>Run with the in-memory vector store component in components/vector.
*/
public class VectorExample {

private static final String STORE_NAME = "vectorstore";
private static final String COLLECTION = "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 Vector Example ===");

client.createVectorCollection(new CreateVectorCollectionRequestAlpha1(STORE_NAME, COLLECTION, 3)
.setMetric(DistanceMetric.COSINE)).block();
System.out.println("Created collection: " + COLLECTION);

// The vectors are made up; an app would get them from an embedding model.
List<VectorRecord> books = List.of(
new VectorRecord("dune", new float[] {0.9f, 0.1f, 0.0f}).setMetadata(Map.of("genre", "scifi")),
new VectorRecord("foundation", new float[] {0.8f, 0.2f, 0.1f}).setMetadata(Map.of("genre", "scifi")),
new VectorRecord("hobbit", new float[] {0.1f, 0.9f, 0.2f}).setMetadata(Map.of("genre", "fantasy")));
UpsertVectorsResponseAlpha1 upsert = client.upsertVectors(
new UpsertVectorsRequestAlpha1(STORE_NAME, COLLECTION, books)
.setOptions(IndexingOptions.waitForCompletion(
Duration.ofSeconds(5), IndexingWaitTimeoutAction.FAIL_REQUEST))).block();
System.out.println("Stored books: " + books.size() + ", " + upsert.getAck()
+ ", failed: " + upsert.getFailedItems().size());

GetVectorCollectionResponseAlpha1 collection = client.getVectorCollection(STORE_NAME, COLLECTION).block();
System.out.println("Collection " + collection.getCollection() + ": " + collection.getDimensions()
+ " dimensions, " + collection.getMetric());

float[] scifiVector = {0.85f, 0.15f, 0.05f};
QueryVectorsResponseAlpha1 closest = client.queryVectors(new QueryVectorsRequestAlpha1(
STORE_NAME, COLLECTION, VectorQuery.byVector(scifiVector).setTopK(2))).block();
System.out.println("Closest two books: " + ids(closest));

QueryVectorsResponseAlpha1 fantasy = client.queryVectors(new QueryVectorsRequestAlpha1(
STORE_NAME, COLLECTION, VectorQuery.byVector(scifiVector)
.setFilter(Map.of("genre", Map.of("$eq", "fantasy"))))).block();
System.out.println("Closest fantasy book: " + ids(fantasy));

// Each query in a batch succeeds or fails on its own.
List<BatchQueryVectorsResultAlpha1> batch = client.batchQueryVectors(new BatchQueryVectorsRequestAlpha1(
STORE_NAME, COLLECTION, List.of(
VectorQuery.byId("hobbit").setTopK(1),
VectorQuery.byId("missing")))).block();
for (int i = 0; i < batch.size(); i++) {
BatchQueryVectorsResultAlpha1 result = batch.get(i);
if (result.getError() != null) {
System.out.println("Batch query " + i + " failed: " + result.getError().getErrorCode());
} else {
System.out.println("Batch query " + i + ": " + ids(result.getResponse()));
}
}

List<VectorRecord> found = client.getVectors(
new GetVectorsRequestAlpha1(STORE_NAME, COLLECTION, List.of("dune", "missing"))).block();
System.out.println("Read records: " + found.stream().map(VectorRecord::getId).collect(Collectors.toList()));

IndexAck deleted = client.deleteVectors(
new DeleteVectorsRequestAlpha1(STORE_NAME, COLLECTION, List.of("dune"))).block();
System.out.println("Deleted record dune: " + deleted);

client.deleteVectorCollection(STORE_NAME, COLLECTION).block();
System.out.println("Deleted collection: " + COLLECTION);
System.out.println("SUCCESS: The vector example finished.");
}
}

private static List<String> ids(QueryVectorsResponseAlpha1 response) {
return response.getMatches().stream()
.map(VectorMatch::getRecord)
.map(VectorRecord::getId)
.collect(Collectors.toList());
}
}
Loading
Loading