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@1.11.0