Common vector store functionality for Spring AI providing a portable abstraction layer for integrating vector databases with comprehensive filtering, similarity search, and observability support.
Core vector database operations for managing documents with embeddings and performing similarity-based searches. The VectorStore interface provides a portable abstraction that works across multiple vector database providers in the Spring AI ecosystem.
The main interface for vector store operations, combining document writing and retrieval capabilities.
package org.springframework.ai.vectorstore;
import org.springframework.ai.document.Document;
import org.springframework.ai.document.DocumentWriter;
import org.springframework.ai.vectorstore.filter.Filter;
import io.micrometer.observation.ObservationRegistry;
import java.util.List;
import java.util.Optional;
/**
* Main interface for managing and querying documents in a vector database.
* Extends DocumentWriter and VectorStoreRetriever for complete CRUD operations.
*
* This interface provides a portable abstraction that works across multiple
* vector database providers including Pinecone, Chroma, Weaviate, PgVector,
* Milvus, Qdrant, Redis, and SimpleVectorStore.
*/
public interface VectorStore extends DocumentWriter, VectorStoreRetriever {
/**
* Returns the name of this vector store implementation.
* Defaults to the simple class name.
*
* Used for logging, metrics, and debugging to identify the vector store type.
*
* @return The name of the vector store (e.g., "SimpleVectorStore", "PineconeVectorStore")
*/
default String getName() {
return this.getClass().getSimpleName();
}
/**
* Adds documents to the vector store.
* Documents will be embedded using the configured EmbeddingModel.
*
* Behavior:
* - If a document already has an embedding, it will be used directly
* - If a document lacks an embedding, the EmbeddingModel will generate one
* - Documents are batched according to the configured BatchingStrategy
* - Duplicate IDs may cause errors (provider-dependent)
*
* @param documents List of Document objects to store
* @throws RuntimeException if the provider detects duplicate IDs
* @throws IllegalArgumentException if documents list is null or empty
*/
void add(List<Document> documents);
/**
* Default implementation of DocumentWriter.accept() that delegates to add().
* Enables VectorStore to be used as a Consumer<List<Document>> in functional pipelines.
*
* @param documents List of documents to accept and store
*/
@Override
default void accept(List<Document> documents) {
this.add(documents);
}
/**
* Deletes documents by their IDs.
*
* Behavior:
* - If an ID doesn't exist, it is silently ignored (no error thrown)
* - Deletion is typically synchronous but may be eventually consistent
* - Empty ID list is a no-op
*
* @param idList List of document IDs to delete
* @throws IllegalArgumentException if idList is null
*/
void delete(List<String> idList);
/**
* Deletes documents matching the filter expression.
* Uses portable Filter.Expression for store-agnostic filtering.
*
* Behavior:
* - Filters are converted to provider-specific format at runtime
* - Deletion is typically synchronous but may be eventually consistent
* - No error if no documents match the filter
*
* @param filterExpression Filter.Expression defining which documents to delete
* @throws IllegalStateException if the underlying delete operation fails
* @throws UnsupportedOperationException if filter-based deletion is not supported by the provider
*/
void delete(Filter.Expression filterExpression);
/**
* Deletes documents using a SQL-like filter string.
* Converts the string to a Filter.Expression and delegates to delete(Expression).
*
* Supported syntax:
* - Comparison: ==, !=, <, <=, >, >=
* - Logical: &&, ||, NOT
* - Inclusion: IN [...], NOT IN [...]
* - Null checks: IS NULL, IS NOT NULL
* - Grouping: ( )
*
* Examples:
* - "year >= 2020 && category == 'old'"
* - "status IN ['archived', 'deleted']"
* - "(year < 2020 OR featured == false) && status != 'active'"
*
* @param filterExpression String filter expression
* @throws IllegalArgumentException if the filter expression is null or invalid syntax
* @throws IllegalStateException if the underlying delete operation fails
* @throws UnsupportedOperationException if filter-based deletion is not supported
*/
default void delete(String filterExpression) {
if (filterExpression == null) {
throw new IllegalArgumentException("Filter expression cannot be null");
}
FilterExpressionTextParser parser = new FilterExpressionTextParser();
Filter.Expression expression = parser.parse(filterExpression);
this.delete(expression);
}
/**
* Performs similarity search using SearchRequest configuration.
* Returns documents ordered by similarity score (highest first).
*
* Process:
* 1. Query text is embedded using the configured EmbeddingModel
* 2. Vector similarity search is performed (typically cosine similarity)
* 3. Results are filtered by metadata filter expression (if provided)
* 4. Results are filtered by similarity threshold (client-side post-processing)
* 5. Top-K results are returned, ordered by similarity score descending
*
* @param request SearchRequest with query, topK, threshold, and filter parameters
* @return List of similar documents ordered by similarity (highest first)
*/
List<Document> similaritySearch(SearchRequest request);
/**
* Convenience method for simple text-based similarity search.
* Uses default topK (4) and no filtering.
*
* Equivalent to:
* SearchRequest.builder()
* .query(query)
* .topK(4)
* .similarityThreshold(0.0)
* .build()
*
* @param query The text query to search for
* @return List of similar documents (up to 4 results)
*/
default List<Document> similaritySearch(String query) {
return this.similaritySearch(SearchRequest.builder().query(query).build());
}
/**
* Returns the native vector store client if available.
* Useful for accessing provider-specific features not exposed by the portable API.
*
* Due to Java type erasure, callers should specify the expected client type:
*
* Example:
* Optional<PineconeClient> client = vectorStore.getNativeClient();
* client.ifPresent(c -> c.describeIndexStats());
*
* @param <T> The type of the native client
* @return Optional containing the native client, or empty if unavailable
*/
default <T> Optional<T> getNativeClient() {
return Optional.empty();
}
/**
* Builder interface for VectorStore implementations.
* Provides fluent API for configuring vector store instances.
*
* @param <T> The concrete builder type for method chaining
*/
interface Builder<T extends Builder<T>> {
/**
* Sets the registry for collecting observations and metrics.
* Defaults to ObservationRegistry.NOOP if not specified.
*
* @param observationRegistry The registry to use for observations
* @return The builder instance for method chaining
*/
T observationRegistry(ObservationRegistry observationRegistry);
/**
* Sets a custom convention for creating observations.
* If not specified, DefaultVectorStoreObservationConvention will be used.
*
* @param convention The custom observation convention to use
* @return The builder instance for method chaining
*/
T customObservationConvention(VectorStoreObservationConvention convention);
/**
* Sets the batching strategy for document operations.
* Defaults to TokenCountBatchingStrategy if not specified.
*
* @param batchingStrategy The strategy to use
* @return The builder instance for method chaining
*/
T batchingStrategy(BatchingStrategy batchingStrategy);
/**
* Builds and returns a new VectorStore instance with the configured settings.
*
* @return A new VectorStore instance
*/
VectorStore build();
}
}Usage Examples:
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.document.Document;
import java.util.List;
import java.util.Map;
// Add documents with rich metadata
List<Document> docs = List.of(
new Document("Spring Boot tutorial", Map.of(
"topic", "spring",
"level", "beginner",
"year", 2024,
"author", "John Doe"
)),
new Document("Advanced Spring concepts", Map.of(
"topic", "spring",
"level", "advanced",
"year", 2024,
"author", "Jane Smith"
))
);
vectorStore.add(docs);
// Simple search
List<Document> results = vectorStore.similaritySearch("Spring Boot basics");
// Delete by IDs
vectorStore.delete(List.of("doc-1", "doc-2"));
// Delete by filter expression
vectorStore.delete("level == 'beginner' && year < 2023");
// Get native client for provider-specific operations
Optional<CustomVectorClient> nativeClient = vectorStore.getNativeClient();
nativeClient.ifPresent(client -> {
// Use provider-specific features
client.performCustomOperation();
});Functional interface for read-only retrieval operations.
package org.springframework.ai.vectorstore;
import org.springframework.ai.document.Document;
import java.util.List;
/**
* Functional interface for vector store retrieval operations.
* Enables VectorStore to be used as a function in retrieval pipelines.
*/
@FunctionalInterface
public interface VectorStoreRetriever {
/**
* Retrieves documents by similarity to the query in SearchRequest.
*
* The search process:
* 1. Query text is converted to an embedding vector
* 2. Vector similarity is computed against all stored documents
* 3. Metadata filters are applied (if specified)
* 4. Results are sorted by similarity score (descending)
* 5. Similarity threshold is applied (client-side filtering)
* 6. Top-K results are returned
*
* @param request SearchRequest with query, topK, threshold, and optional filters
* @return List of documents ordered by similarity (highest first)
*/
List<Document> similaritySearch(SearchRequest request);
/**
* Convenience method for simple text-based similarity search.
* Uses default topK (4) and no filtering.
*
* @param query The text query to search for
* @return List of similar documents
*/
default List<Document> similaritySearch(String query) {
return this.similaritySearch(
SearchRequest.builder()
.query(query)
.topK(SearchRequest.DEFAULT_TOP_K)
.similarityThresholdAll()
.build()
);
}
}Usage Examples:
// Simple text search
List<Document> results = vectorStore.similaritySearch("What is Spring Boot?");
// Advanced search with SearchRequest
SearchRequest request = SearchRequest.builder()
.query("machine learning basics")
.topK(10)
.similarityThreshold(0.75)
.filterExpression("category == 'AI' && year >= 2023")
.build();
List<Document> filteredResults = vectorStore.similaritySearch(request);
// Use as a function in a pipeline
Function<String, List<Document>> retriever = vectorStore::similaritySearch;
List<Document> pipelineResults = retriever.apply("search query");Immutable configuration object for similarity search operations.
package org.springframework.ai.vectorstore;
import org.springframework.ai.vectorstore.filter.Filter;
import org.springframework.ai.vectorstore.filter.FilterExpressionTextParser;
import org.springframework.lang.Nullable;
/**
* Configuration for similarity search requests.
* Use SearchRequest.builder() to create instances.
*
* This class is immutable and thread-safe.
*/
public class SearchRequest {
/**
* Similarity threshold that accepts all scores (0.0 means no threshold filtering).
* Use this constant when you want to retrieve all results regardless of similarity.
*/
public static final double SIMILARITY_THRESHOLD_ACCEPT_ALL = 0.0;
/**
* Default number of top results to return.
* This is the default value used when topK is not explicitly specified.
*/
public static final int DEFAULT_TOP_K = 4;
private final String query;
private final int topK;
private final double similarityThreshold;
private final Filter.Expression filterExpression;
/**
* Default public no-arg constructor creating a SearchRequest with default values.
* Query: null, topK: 4, similarityThreshold: 0.0, filterExpression: null
* Primarily used by frameworks and serialization.
*/
public SearchRequest() {
this(null, DEFAULT_TOP_K, SIMILARITY_THRESHOLD_ACCEPT_ALL, null);
}
/**
* Protected copy constructor - use builder() or from() to create instances.
* Copies all values from the original SearchRequest.
* Not intended for direct use by application code.
*
* @param original SearchRequest to copy from
*/
protected SearchRequest(SearchRequest original) {
this(original.query, original.topK, original.similarityThreshold, original.filterExpression);
}
/**
* Private constructor used by builder.
*/
private SearchRequest(String query, int topK, double similarityThreshold,
Filter.Expression filterExpression) {
this.query = query;
this.topK = topK;
this.similarityThreshold = similarityThreshold;
this.filterExpression = filterExpression;
}
/**
* Returns the text query for embedding similarity comparison.
*
* @return The query text, or null if not set
*/
public String getQuery() {
return this.query;
}
/**
* Returns the number of top similar results to return.
*
* @return The top-K value (always >= 0)
*/
public int getTopK() {
return this.topK;
}
/**
* Returns the minimum similarity threshold for filtering results.
* Only documents with similarity >= threshold are returned.
* This is a client-side post-processing filter.
*
* @return The similarity threshold in range [0.0, 1.0]
*/
public double getSimilarityThreshold() {
return this.similarityThreshold;
}
/**
* Returns the filter expression for metadata filtering.
*
* @return The filter expression, or null if no filtering
*/
@Nullable
public Filter.Expression getFilterExpression() {
return this.filterExpression;
}
/**
* Checks if a filter expression is present.
*
* @return true if a filter expression is set, false otherwise
*/
public boolean hasFilterExpression() {
return this.filterExpression != null;
}
/**
* Creates a new SearchRequest builder.
*
* @return A new Builder instance with default values
*/
public static Builder builder() {
return new Builder();
}
/**
* Creates a builder initialized with values from an existing SearchRequest.
* Useful for creating modified copies of existing requests.
*
* @param originalSearchRequest The request to copy values from
* @return A new Builder instance initialized with the original values
*/
public static Builder from(SearchRequest originalSearchRequest) {
return new Builder(originalSearchRequest);
}
@Override
public String toString() {
return "SearchRequest{" +
"query='" + query + '\'' +
", topK=" + topK +
", similarityThreshold=" + similarityThreshold +
", hasFilter=" + hasFilterExpression() +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SearchRequest that = (SearchRequest) o;
return topK == that.topK &&
Double.compare(that.similarityThreshold, similarityThreshold) == 0 &&
Objects.equals(query, that.query) &&
Objects.equals(filterExpression, that.filterExpression);
}
@Override
public int hashCode() {
return Objects.hash(query, topK, similarityThreshold, filterExpression);
}
/**
* Builder for SearchRequest with fluent API.
*/
public static final class Builder {
private String query;
private int topK = DEFAULT_TOP_K;
private double similarityThreshold = SIMILARITY_THRESHOLD_ACCEPT_ALL;
private Filter.Expression filterExpression;
/**
* Default constructor creating a builder with default values.
*/
private Builder() {
}
/**
* Copy constructor creating a builder from an existing SearchRequest.
*/
private Builder(SearchRequest original) {
this.query = original.query;
this.topK = original.topK;
this.similarityThreshold = original.similarityThreshold;
this.filterExpression = original.filterExpression;
}
/**
* Sets the text query for embedding similarity comparison.
*
* @param query Text to convert to embedding and compare against stored documents
* @return This builder for chaining
* @throws IllegalArgumentException if query is null
*/
public Builder query(String query) {
if (query == null) {
throw new IllegalArgumentException("Query cannot be null");
}
this.query = query;
return this;
}
/**
* Sets the number of top similar results to return.
*
* @param topK Number of results (must be >= 0)
* @return This builder for chaining
* @throws IllegalArgumentException if topK is negative
*/
public Builder topK(int topK) {
if (topK < 0) {
throw new IllegalArgumentException("topK must be >= 0, got: " + topK);
}
this.topK = topK;
return this;
}
/**
* Sets the minimum similarity threshold for filtering results.
* Only documents with similarity >= threshold are returned.
* This is a client-side post-processing filter applied after retrieval.
*
* Note: This filter is applied AFTER the vector store returns results,
* so it does not improve query performance. Use metadata filters for
* server-side filtering.
*
* @param threshold Similarity threshold in range [0.0, 1.0]
* 0.0 = accept all, 1.0 = exact match required
* @return This builder for chaining
* @throws IllegalArgumentException if threshold not in [0.0, 1.0]
*/
public Builder similarityThreshold(double threshold) {
if (threshold < 0.0 || threshold > 1.0) {
throw new IllegalArgumentException(
"Similarity threshold must be in range [0.0, 1.0], got: " + threshold
);
}
this.similarityThreshold = threshold;
return this;
}
/**
* Disables similarity threshold filtering by setting it to 0.0.
* All results will be returned regardless of similarity score.
*
* @return This builder for chaining
*/
public Builder similarityThresholdAll() {
this.similarityThreshold = SIMILARITY_THRESHOLD_ACCEPT_ALL;
return this;
}
/**
* Sets a programmatic filter expression for metadata filtering.
* The filter is portable across all vector store implementations.
*
* This filter is applied SERVER-SIDE (within the vector store),
* which can significantly improve query performance by reducing
* the number of documents that need to be compared.
*
* @param expression Filter.Expression or null for no filtering
* @return This builder for chaining
*/
public Builder filterExpression(@Nullable Filter.Expression expression) {
this.filterExpression = expression;
return this;
}
/**
* Sets a SQL-like text filter expression for metadata filtering.
* The expression is parsed and converted to a portable Filter.Expression.
*
* Supported syntax:
* - Comparison: ==, !=, <, <=, >, >=
* - Logical: &&, ||, NOT
* - Inclusion: IN [...], NOT IN [...]
* - Null checks: IS NULL, IS NOT NULL
* - Grouping: ( )
* - Literals: strings ('text'), numbers, booleans (true, false)
*
* Examples:
* - "country == 'UK' && year >= 2020"
* - "category IN ['tech', 'science'] && active == true"
* - "(year > 2020 OR featured == true) && status != 'archived'"
*
* @param textExpression SQL-like filter string or null for no filtering
* @return This builder for chaining
* @throws FilterExpressionParseException if the expression has invalid syntax
*/
public Builder filterExpression(@Nullable String textExpression) {
if (textExpression == null) {
this.filterExpression = null;
} else {
FilterExpressionTextParser parser = new FilterExpressionTextParser();
this.filterExpression = parser.parse(textExpression);
}
return this;
}
/**
* Builds the immutable SearchRequest.
*
* @return A new SearchRequest instance with the configured values
*/
public SearchRequest build() {
return new SearchRequest(query, topK, similarityThreshold, filterExpression);
}
}
}Usage Examples:
import org.springframework.ai.vectorstore.SearchRequest;
// Basic search with defaults (topK=4, no threshold, no filter)
SearchRequest simple = SearchRequest.builder()
.query("Spring Framework")
.build();
// Advanced search with all parameters
SearchRequest advanced = SearchRequest.builder()
.query("machine learning algorithms")
.topK(20)
.similarityThreshold(0.8)
.filterExpression("category == 'AI' && (year >= 2023 OR featured == true)")
.build();
// Copy and modify existing request
SearchRequest modified = SearchRequest.from(advanced)
.topK(10)
.similarityThreshold(0.7)
.build();
// Search accepting all similarity scores
SearchRequest acceptAll = SearchRequest.builder()
.query("general search")
.topK(50)
.similarityThresholdAll()
.build();
// Search with programmatic filter
FilterExpressionBuilder b = new FilterExpressionBuilder();
SearchRequest programmatic = SearchRequest.builder()
.query("search text")
.filterExpression(b.and(
b.gte("year", 2023),
b.in("category", "tech", "science")
).build())
.build();In-memory vector store implementation with JSON persistence support.
package org.springframework.ai.vectorstore;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.observation.AbstractObservationVectorStore;
import org.springframework.core.io.Resource;
import java.io.File;
import java.io.IOException;
/**
* Simple in-memory vector store implementation.
* Suitable for testing, development, and small-scale deployments.
* Supports saving/loading state to/from JSON files.
*
* Features:
* - In-memory storage using ConcurrentHashMap for thread-safety
* - Cosine similarity for vector comparison
* - SpEL-based filter expression evaluation
* - JSON persistence for state management
* - Full observability support via Micrometer
*
* Limitations:
* - Not suitable for large datasets (> 10,000 documents)
* - No distributed support
* - Limited to single-node deployments
* - Performance degrades linearly with document count
*
* For production use cases, consider:
* - Pinecone (cloud-managed, scalable)
* - Chroma (open-source, self-hosted)
* - PgVector (PostgreSQL extension)
* - Milvus (distributed vector database)
*/
public class SimpleVectorStore extends AbstractObservationVectorStore {
/**
* Creates a builder for SimpleVectorStore.
*
* @param embeddingModel The embedding model to use for document vectorization
* @return SimpleVectorStoreBuilder instance
* @throws IllegalArgumentException if embeddingModel is null
*/
public static SimpleVectorStoreBuilder builder(EmbeddingModel embeddingModel) {
return new SimpleVectorStoreBuilder(embeddingModel);
}
/**
* Saves the current vector store state to a JSON file.
* The file contains all documents with their embeddings and metadata.
*
* JSON format:
* [
* {
* "id": "doc-1",
* "text": "document content",
* "metadata": {"key": "value"},
* "embedding": [0.1, 0.2, ...]
* },
* ...
* ]
*
* @param file Target file for saving
* @throws RuntimeException if writing fails
*/
public void save(File file);
/**
* Loads vector store state from a JSON file.
* Replaces all existing documents in the store.
*
* The JSON file must have the format produced by save().
*
* @param file Source file to load from
* @throws IOException if reading or parsing fails
* @throws IllegalArgumentException if file is null or doesn't exist
*/
public void load(File file) throws IOException;
/**
* Loads vector store state from a Spring Resource.
* Supports classpath resources, file system resources, and URL resources.
*
* Examples:
* - new ClassPathResource("data/vectorstore.json")
* - new FileSystemResource("/path/to/vectorstore.json")
* - new UrlResource("https://example.com/vectorstore.json")
*
* @param resource Spring Resource to load from
* @throws IOException if reading or parsing fails
* @throws IllegalArgumentException if resource is null or doesn't exist
*/
public void load(Resource resource) throws IOException;
/**
* Mathematical operations for embedding vectors.
* Provides utility methods for vector similarity calculations.
*/
public static class EmbeddingMath {
/**
* Computes cosine similarity between two embedding vectors.
* Returns a value in range [-1.0, 1.0] where:
* - 1.0 = identical direction (perfect similarity)
* - 0.0 = orthogonal (no similarity)
* - -1.0 = opposite direction (perfect dissimilarity)
*
* Formula: cosine_similarity(A, B) = (A · B) / (||A|| * ||B||)
*
* @param vectorX First embedding vector
* @param vectorY Second embedding vector
* @return Cosine similarity score in range [-1.0, 1.0]
* @throws IllegalArgumentException if vectors have different dimensions
*/
public static double cosineSimilarity(float[] vectorX, float[] vectorY);
/**
* Computes dot product of two embedding vectors.
* The dot product is the sum of element-wise products.
*
* Formula: dot_product(A, B) = Σ(A[i] * B[i])
*
* @param vectorX First embedding vector
* @param vectorY Second embedding vector
* @return Dot product result
* @throws IllegalArgumentException if vectors have different dimensions
*/
public static float dotProduct(float[] vectorX, float[] vectorY);
/**
* Computes the Euclidean norm (magnitude) of an embedding vector.
* The norm is the square root of the sum of squared elements.
*
* Formula: norm(A) = sqrt(Σ(A[i]²))
*
* @param vector Embedding vector
* @return Vector norm (magnitude), always >= 0
*/
public static float norm(float[] vector);
}
}Usage Examples:
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.embedding.EmbeddingModel;
import io.micrometer.observation.ObservationRegistry;
import java.io.File;
import java.io.IOException;
// Create basic SimpleVectorStore
SimpleVectorStore store = SimpleVectorStore.builder(embeddingModel).build();
// Create with observation support
SimpleVectorStore observableStore = SimpleVectorStore.builder(embeddingModel)
.observationRegistry(observationRegistry)
.customObservationConvention(new DefaultVectorStoreObservationConvention())
.build();
// Add documents and save to file
store.add(documents);
store.save(new File("vectorstore.json"));
// Load from file
SimpleVectorStore loadedStore = SimpleVectorStore.builder(embeddingModel).build();
try {
loadedStore.load(new File("vectorstore.json"));
System.out.println("Loaded existing vector store");
} catch (IOException e) {
System.err.println("Could not load vector store: " + e.getMessage());
// Start with empty store
}
// Load from classpath resource
import org.springframework.core.io.ClassPathResource;
loadedStore.load(new ClassPathResource("data/vectorstore.json"));
// Use EmbeddingMath utilities
float[] embedding1 = {0.5f, 0.3f, 0.8f};
float[] embedding2 = {0.6f, 0.4f, 0.7f};
double similarity = SimpleVectorStore.EmbeddingMath.cosineSimilarity(embedding1, embedding2);
float dotProd = SimpleVectorStore.EmbeddingMath.dotProduct(embedding1, embedding2);
float magnitude = SimpleVectorStore.EmbeddingMath.norm(embedding1);
System.out.println("Cosine similarity: " + similarity);
System.out.println("Dot product: " + dotProd);
System.out.println("Vector magnitude: " + magnitude);Builder for configuring SimpleVectorStore instances.
package org.springframework.ai.vectorstore;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.document.BatchingStrategy;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
import io.micrometer.observation.ObservationRegistry;
/**
* Builder for SimpleVectorStore with fluent API.
* Extends AbstractVectorStoreBuilder for common configuration.
*/
public static class SimpleVectorStoreBuilder
extends AbstractVectorStoreBuilder<SimpleVectorStoreBuilder> {
/**
* Package-private constructor - use SimpleVectorStore.builder() to create.
*
* @param embeddingModel The embedding model (required)
*/
SimpleVectorStoreBuilder(EmbeddingModel embeddingModel) {
super(embeddingModel);
}
/**
* Sets the observation registry for metrics collection.
* Defaults to ObservationRegistry.NOOP if not specified.
*
* @param observationRegistry Micrometer observation registry
* @return This builder for chaining
* @throws IllegalArgumentException if observationRegistry is null
*/
@Override
public SimpleVectorStoreBuilder observationRegistry(ObservationRegistry observationRegistry);
/**
* Sets a custom observation convention for metrics naming.
* Defaults to DefaultVectorStoreObservationConvention if not specified.
*
* @param convention Custom observation convention
* @return This builder for chaining
*/
@Override
public SimpleVectorStoreBuilder customObservationConvention(
VectorStoreObservationConvention convention
);
/**
* Sets the batching strategy for document operations.
* Defaults to TokenCountBatchingStrategy if not specified.
*
* @param batchingStrategy Strategy for batching add operations
* @return This builder for chaining
* @throws IllegalArgumentException if batchingStrategy is null
*/
@Override
public SimpleVectorStoreBuilder batchingStrategy(BatchingStrategy batchingStrategy);
/**
* Builds the SimpleVectorStore instance.
*
* @return A new SimpleVectorStore with the configured settings
*/
@Override
public SimpleVectorStore build();
}Usage Examples:
// Minimal configuration
SimpleVectorStore minimal = SimpleVectorStore.builder(embeddingModel)
.build();
// Full configuration
SimpleVectorStore full = SimpleVectorStore.builder(embeddingModel)
.observationRegistry(observationRegistry)
.customObservationConvention(new DefaultVectorStoreObservationConvention())
.batchingStrategy(new TokenCountBatchingStrategy())
.build();
// With custom batching
BatchingStrategy customBatching = new TokenCountBatchingStrategy(
embeddingModel,
1000, // max tokens per batch
100 // max documents per batch
);
SimpleVectorStore customBatch = SimpleVectorStore.builder(embeddingModel)
.batchingStrategy(customBatching)
.build();import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.document.Document;
import java.util.List;
import java.util.Map;
// Create vector store
SimpleVectorStore store = SimpleVectorStore.builder(embeddingModel).build();
// CREATE: Add documents
List<Document> documents = List.of(
new Document("Spring Boot makes it easy to create stand-alone applications",
Map.of("category", "framework", "year", 2024)),
new Document("Java is a popular programming language",
Map.of("category", "language", "year", 2023)),
new Document("Vector databases enable semantic search",
Map.of("category", "database", "year", 2024))
);
store.add(documents);
// READ: Search for similar documents
SearchRequest searchRequest = SearchRequest.builder()
.query("What is Spring Boot?")
.topK(2)
.similarityThreshold(0.7)
.build();
List<Document> results = store.similaritySearch(searchRequest);
results.forEach(doc -> {
System.out.println("Content: " + doc.getContent());
System.out.println("Score: " + doc.getMetadata().get("distance"));
System.out.println("Metadata: " + doc.getMetadata());
});
// UPDATE: Modify by deleting and re-adding
store.delete(List.of("doc-id-1"));
Document updated = new Document("doc-id-1", "Updated content",
Map.of("category", "framework", "year", 2024, "updated", true));
store.add(List.of(updated));
// DELETE: Remove documents
store.delete(List.of("doc-id-2", "doc-id-3"));
// DELETE: Remove by filter
store.delete("year < 2023");import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
FilterExpressionBuilder b = new FilterExpressionBuilder();
// Complex filter with multiple conditions
SearchRequest complexSearch = SearchRequest.builder()
.query("machine learning tutorial")
.topK(10)
.similarityThreshold(0.75)
.filterExpression(b.and(
b.or(
b.gte("year", 2023),
b.eq("featured", true)
),
b.ne("status", "archived"),
b.in("category", "AI", "ML", "Data Science")
).build())
.build();
List<Document> filteredResults = store.similaritySearch(complexSearch);import java.io.File;
import java.io.IOException;
File storeFile = new File("vectorstore.json");
// Save state periodically
try {
store.save(storeFile);
logger.info("Vector store saved successfully");
} catch (Exception e) {
logger.error("Failed to save vector store", e);
}
// Load state on startup
SimpleVectorStore recoveredStore = SimpleVectorStore.builder(embeddingModel).build();
try {
recoveredStore.load(storeFile);
logger.info("Vector store loaded successfully");
} catch (IOException e) {
logger.warn("Could not load vector store, starting fresh", e);
// Initialize with default data if needed
recoveredStore.add(getDefaultDocuments());
}import io.micrometer.observation.ObservationRegistry;
import org.springframework.ai.document.TokenCountBatchingStrategy;
// Configure with observability
ObservationRegistry registry = ObservationRegistry.create();
BatchingStrategy batching = new TokenCountBatchingStrategy();
SimpleVectorStore observableStore = SimpleVectorStore.builder(embeddingModel)
.observationRegistry(registry)
.batchingStrategy(batching)
.build();
// Add large batch of documents - will be automatically batched
List<Document> largeBatch = generateLargeDocumentSet(10000);
observableStore.add(largeBatch); // Batched automatically
// Metrics are collected automatically
registry.getMeters().forEach(meter ->
System.out.println(meter.getId() + ": " + meter.measure())
);// Handle duplicate IDs
try {
store.add(documentsWithDuplicateIds);
} catch (RuntimeException e) {
logger.error("Duplicate document IDs detected", e);
// Generate new IDs or update existing documents
}
// Handle empty results
List<Document> results = store.similaritySearch(request);
if (results.isEmpty()) {
logger.warn("No results found for query: {}", request.getQuery());
// Consider lowering threshold or broadening filter
SearchRequest relaxedRequest = SearchRequest.from(request)
.similarityThreshold(0.5)
.build();
results = store.similaritySearch(relaxedRequest);
}
// Handle unsupported operations
try {
store.delete("category == 'old'");
} catch (UnsupportedOperationException e) {
// Fall back to ID-based deletion
logger.warn("Filter-based deletion not supported, using ID-based deletion");
List<String> idsToDelete = findDocumentIds("category == 'old'");
store.delete(idsToDelete);
}Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-ai--spring-ai-vector-store