LangChain4j integration for Chroma embedding store enabling storage, retrieval, and similarity search of vector embeddings with metadata filtering support for both API V1 and V2.
—
Finding similar embeddings in the Chroma vector store using cosine similarity.
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingSearchResult;
import dev.langchain4j.data.embedding.Embedding;
Embedding queryEmbedding = Embedding.from(new float[]{0.15f, 0.25f, 0.35f});
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(10)
.build();
EmbeddingSearchResult<TextSegment> result = store.search(request);EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(10)
.minScore(0.7) // Only return matches with score >= 0.7
.build();
EmbeddingSearchResult<TextSegment> result = store.search(request);import dev.langchain4j.store.embedding.EmbeddingMatch;
EmbeddingSearchResult<TextSegment> result = store.search(request);
for (EmbeddingMatch<TextSegment> match : result.matches()) {
// Similarity score (0.0 to 1.0, higher is more similar)
double score = match.score();
// Embedding ID
String embeddingId = match.embeddingId();
// The matching embedding vector
Embedding matchedEmbedding = match.embedding();
// Associated text segment (may be null)
TextSegment textSegment = match.embedded();
}for (EmbeddingMatch<TextSegment> match : result.matches()) {
TextSegment segment = match.embedded();
if (segment != null) {
// Get text content
String text = segment.text();
// Get metadata
Metadata metadata = segment.metadata();
if (metadata != null) {
String author = metadata.getString("author");
Integer year = metadata.getInteger("year");
}
}
}import java.util.stream.Collectors;
List<String> relevantTexts = result.matches().stream()
.filter(match -> match.score() > 0.8)
.map(match -> match.embedded().text())
.collect(Collectors.toList());import dev.langchain4j.store.embedding.filter.Filter;
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.*;
Filter filter = metadataKey("author").isEqualTo("John Doe");
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(10)
.filter(filter)
.build();// Greater than or equal
Filter yearFilter = metadataKey("year").isGreaterThanOrEqualTo(2020);
// Greater than
Filter scoreFilter = metadataKey("score").isGreaterThan(7.5);
// Less than or equal
Filter priceFilter = metadataKey("price").isLessThanOrEqualTo(100.0);
// Less than
Filter ageFilter = metadataKey("age").isLessThan(30);Important: Comparison operators only work with numeric metadata values in Chroma.
// Match any of the values
Filter categoryFilter = metadataKey("category")
.isIn(Arrays.asList("tech", "science", "math"));
// Exclude values
Filter excludeFilter = metadataKey("status")
.isNotIn(Arrays.asList("draft", "archived"));Filter combined = metadataKey("status").isEqualTo("published")
.and(metadataKey("year").isGreaterThanOrEqualTo(2020))
.and(metadataKey("author").isEqualTo("John Doe"));
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(5)
.filter(combined)
.build();Filter orFilter = metadataKey("category").isEqualTo("tech")
.or(metadataKey("category").isEqualTo("science"))
.or(metadataKey("category").isEqualTo("math"));// (status = "published") AND ((priority >= 5) OR (urgent = true))
Filter complex = metadataKey("status").isEqualTo("published")
.and(
metadataKey("priority").isGreaterThanOrEqualTo(5)
.or(metadataKey("urgent").isEqualTo(true))
);// Exclude specific value
Filter notArchived = metadataKey("status").isNotEqualTo("archived");
// Alternative using NOT wrapper (automatically converted)
Filter notDraft = Filter.not(metadataKey("status").isEqualTo("draft"));Note: Chroma doesn't natively support NOT operations. The library converts them to equivalent positive operations where possible.
// Chroma uses cosine distance
// distance range: [0, 2]
// score = 1 - (distance / 2)
// score range: [0, 1]
// Examples:
// distance = 0.0 → score = 1.0 (perfect match)
// distance = 1.0 → score = 0.5 (orthogonal)
// distance = 2.0 → score = 0.0 (opposite)Common threshold guidelines:
// Very strict - only near-perfect matches
.minScore(0.95)
// Strict - very similar content
.minScore(0.85)
// Moderate - reasonably similar
.minScore(0.70)
// Loose - any similarity
.minScore(0.50)
// Very loose - include marginally related
.minScore(0.30)import dev.langchain4j.model.embedding.EmbeddingModel;
String userQuery = "What is machine learning?";
// Convert query to embedding
EmbeddingModel model = createEmbeddingModel();
Embedding queryEmbedding = model.embed(userQuery).content();
// Search for similar content
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(5)
.minScore(0.7)
.build();
List<String> relevantDocs = store.search(request).matches().stream()
.map(match -> match.embedded().text())
.collect(Collectors.toList());// Retrieve context for RAG (Retrieval-Augmented Generation)
String question = "How does authentication work?";
Embedding queryEmbedding = model.embed(question).content();
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(3)
.minScore(0.75)
.build();
String context = store.search(request).matches().stream()
.map(match -> match.embedded().text())
.collect(Collectors.joining("\n\n"));
// Use context with LLM
String prompt = "Context:\n" + context + "\n\nQuestion: " + question;String query = "recent AI advances";
Embedding queryEmbedding = model.embed(query).content();
// Only search in recent, published documents
Filter filter = metadataKey("status").isEqualTo("published")
.and(metadataKey("year").isGreaterThanOrEqualTo(2023))
.and(metadataKey("category").isEqualTo("AI"));
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(10)
.minScore(0.8)
.filter(filter)
.build();// Find near-duplicate content
Embedding documentEmbedding = getDocumentEmbedding();
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(documentEmbedding)
.maxResults(10)
.minScore(0.95) // Very high threshold for near-duplicates
.build();
// Filter out the document itself if needed
List<EmbeddingMatch<TextSegment>> duplicates =
store.search(request).matches().stream()
.filter(match -> !match.embeddingId().equals(originalDocId))
.collect(Collectors.toList());// Search within specific categories
List<String> allowedCategories = Arrays.asList("tech", "science", "engineering");
Filter categoryFilter = metadataKey("category").isIn(allowedCategories);
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(20)
.filter(categoryFilter)
.build();// Search only recent documents (last 90 days)
long ninetyDaysAgo = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000);
Filter timeFilter = metadataKey("timestamp").isGreaterThanOrEqualTo(ninetyDaysAgo);
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(15)
.filter(timeFilter)
.build();// Smaller maxResults = faster search
.maxResults(5) // Fast, good for most use cases
.maxResults(50) // Moderate, for broader exploration
.maxResults(100) // Slower, for comprehensive results// minScore acts as early termination hint
// Can improve performance by reducing result processing
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(100)
.minScore(0.8) // May return fewer than 100 results
.build();More selective filters improve performance:
// Less selective - scans more data
Filter broad = metadataKey("year").isGreaterThan(2000);
// More selective - scans less data
Filter specific = metadataKey("status").isEqualTo("published")
.and(metadataKey("year").isEqualTo(2024))
.and(metadataKey("category").isEqualTo("AI"));try {
EmbeddingSearchResult<TextSegment> result = store.search(request);
} catch (IllegalArgumentException e) {
// Invalid request parameters
System.err.println("Invalid search request: " + e.getMessage());
} catch (java.net.http.HttpTimeoutException e) {
// Search timed out
System.err.println("Search timed out: " + e.getMessage());
} catch (Exception e) {
// Other errors (network, Chroma errors)
System.err.println("Search failed: " + e.getMessage());
}Filter Limitations:
Embedding Limitations:
See: EmbeddingSearchRequest API for complete type signatures.
Related:
Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-chroma