LangChain4j Qdrant integration providing a vector store embedding implementation for Qdrant database with metadata filtering support
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Complete guide to metadata filtering in QdrantEmbeddingStore.
The library supports complex metadata filtering through the LangChain4j filter API. Filters are automatically converted to Qdrant's native filter format and can be used in:
import dev.langchain4j.store.embedding.filter.Filter;
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.*;
// Simple equality
Filter filter = metadataKey("category").isEqualTo("documentation");
// Comparison
Filter filter = metadataKey("year").isGreaterThan(2020);
// String search
Filter filter = metadataKey("content").containsString("important");
// Collection membership
Filter filter = metadataKey("status").isIn(Arrays.asList("active", "pending"));
// Logical combination
Filter filter = new And(
metadataKey("category").isEqualTo("tech"),
metadataKey("priority").isGreaterThan(5)
);import dev.langchain4j.store.embedding.filter.comparison.IsEqualTo;
import dev.langchain4j.store.embedding.filter.comparison.IsNotEqualTo;
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.*;
// String equality
Filter filter1 = metadataKey("category").isEqualTo("documentation");
Filter filter2 = new IsEqualTo("category", "documentation");
// Integer equality
Filter filter3 = metadataKey("count").isEqualTo(42);
Filter filter4 = new IsEqualTo("count", 42);
// Long equality
Filter filter5 = metadataKey("timestamp").isEqualTo(1234567890L);
Filter filter6 = new IsEqualTo("timestamp", 1234567890L);
// UUID equality
import java.util.UUID;
UUID documentId = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
Filter filter7 = metadataKey("documentId").isEqualTo(documentId);
Filter filter8 = new IsEqualTo("documentId", documentId);
// Not equal
Filter filter9 = metadataKey("status").isNotEqualTo("archived");
Filter filter10 = new IsNotEqualTo("status", "archived");Supported Types: String, UUID, Integer, Long
import dev.langchain4j.store.embedding.filter.comparison.IsGreaterThan;
import dev.langchain4j.store.embedding.filter.comparison.IsGreaterThanOrEqualTo;
import dev.langchain4j.store.embedding.filter.comparison.IsLessThan;
import dev.langchain4j.store.embedding.filter.comparison.IsLessThanOrEqualTo;
// Greater than
Filter filter1 = metadataKey("score").isGreaterThan(7.5);
Filter filter2 = new IsGreaterThan("score", 7.5);
// Greater than or equal
Filter filter3 = metadataKey("year").isGreaterThanOrEqualTo(2020);
Filter filter4 = new IsGreaterThanOrEqualTo("year", 2020);
// Less than
Filter filter5 = metadataKey("price").isLessThan(100.0);
Filter filter6 = new IsLessThan("price", 100.0);
// Less than or equal
Filter filter7 = metadataKey("age").isLessThanOrEqualTo(65);
Filter filter8 = new IsLessThanOrEqualTo("age", 65);Supported Types: Integer, Long, Double, Float
import dev.langchain4j.store.embedding.filter.comparison.IsIn;
import dev.langchain4j.store.embedding.filter.comparison.IsNotIn;
import java.util.Arrays;
import java.util.Collection;
// String collection
Collection<String> statuses = Arrays.asList("active", "pending", "review");
Filter filter1 = metadataKey("status").isIn(statuses);
Filter filter2 = new IsIn("status", statuses);
// Integer collection
Collection<Integer> priorities = Arrays.asList(1, 2, 3);
Filter filter3 = metadataKey("priority").isIn(priorities);
Filter filter4 = new IsIn("priority", priorities);
// Long collection
Collection<Long> timestamps = Arrays.asList(1000L, 2000L, 3000L);
Filter filter5 = metadataKey("timestamp").isIn(timestamps);
// UUID collection
import java.util.UUID;
Collection<UUID> ids = Arrays.asList(
UUID.fromString("550e8400-e29b-41d4-a716-446655440000"),
UUID.fromString("550e8400-e29b-41d4-a716-446655440001")
);
Filter filter6 = metadataKey("documentId").isIn(ids);
// Not in collection
Filter filter7 = metadataKey("category").isNotIn(Arrays.asList("spam", "archived"));
Filter filter8 = new IsNotIn("category", Arrays.asList("spam", "archived"));Supported Types: String, UUID, Integer, Long
import dev.langchain4j.store.embedding.filter.comparison.ContainsString;
// Substring search (case-sensitive)
Filter filter1 = metadataKey("title").containsString("important");
Filter filter2 = new ContainsString("title", "important");
// Partial match
Filter filter3 = metadataKey("content").containsString("user guide");Note: String matching is case-sensitive.
import dev.langchain4j.store.embedding.filter.logical.And;
// Combine two conditions
Filter filter1 = new And(
metadataKey("category").isEqualTo("documentation"),
metadataKey("year").isGreaterThanOrEqualTo(2020)
);
// Chain multiple AND conditions
Filter filter2 = new And(
metadataKey("status").isEqualTo("published"),
new And(
metadataKey("priority").isGreaterThan(5),
metadataKey("author").isEqualTo("John Doe")
)
);import dev.langchain4j.store.embedding.filter.logical.Or;
// Either condition
Filter filter1 = new Or(
metadataKey("priority").isEqualTo(1),
metadataKey("urgent").isEqualTo(true)
);
// Chain multiple OR conditions
Filter filter2 = new Or(
metadataKey("category").isEqualTo("bug"),
new Or(
metadataKey("category").isEqualTo("critical"),
metadataKey("category").isEqualTo("security")
)
);import dev.langchain4j.store.embedding.filter.logical.Not;
// Negate condition
Filter filter1 = new Not(
metadataKey("status").isEqualTo("archived")
);
// Negate complex condition
Filter filter2 = new Not(
new And(
metadataKey("category").isEqualTo("spam"),
metadataKey("score").isLessThan(3)
)
);// (category = "tech" AND year >= 2020) OR (priority > 8)
Filter filter1 = new Or(
new And(
metadataKey("category").isEqualTo("tech"),
metadataKey("year").isGreaterThanOrEqualTo(2020)
),
metadataKey("priority").isGreaterThan(8)
);
// category = "documentation" AND NOT(status = "draft" OR status = "archived")
Filter filter2 = new And(
metadataKey("category").isEqualTo("documentation"),
new Not(
new Or(
metadataKey("status").isEqualTo("draft"),
metadataKey("status").isEqualTo("archived")
)
)
);
// (author = "Alice" OR author = "Bob") AND year > 2020 AND status != "deleted"
Filter filter3 = new And(
new Or(
metadataKey("author").isEqualTo("Alice"),
metadataKey("author").isEqualTo("Bob")
),
new And(
metadataKey("year").isGreaterThan(2020),
metadataKey("status").isNotEqualTo("deleted")
)
);import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingSearchResult;
import dev.langchain4j.data.segment.TextSegment;
Filter filter = metadataKey("category").isEqualTo("documentation");
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(10)
.minScore(0.7)
.filter(filter)
.build();
EmbeddingSearchResult<TextSegment> results = store.search(request);// Find documents from 2020-2023
Filter dateFilter = new And(
metadataKey("year").isGreaterThanOrEqualTo(2020),
metadataKey("year").isLessThanOrEqualTo(2023)
);
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(20)
.filter(dateFilter)
.build();
EmbeddingSearchResult<TextSegment> results = store.search(request);// Find documents in specific categories
Filter categoryFilter = metadataKey("category").isIn(
Arrays.asList("tutorial", "guide", "documentation")
);
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(15)
.filter(categoryFilter)
.build();
EmbeddingSearchResult<TextSegment> results = store.search(request);// Find high-quality, recent documents
Filter qualityFilter = new And(
metadataKey("quality_score").isGreaterThan(7.0),
new And(
metadataKey("year").isGreaterThanOrEqualTo(2022),
metadataKey("status").isNotEqualTo("deprecated")
)
);
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(10)
.minScore(0.8)
.filter(qualityFilter)
.build();
EmbeddingSearchResult<TextSegment> results = store.search(request);Filter filter = metadataKey("category").isEqualTo("temporary");
store.removeAll(filter);// Remove documents older than 2020
Filter filter = metadataKey("year").isLessThan(2020);
store.removeAll(filter);// Remove draft documents from specific author
Filter filter = new And(
metadataKey("status").isEqualTo("draft"),
metadataKey("author").isEqualTo("test_user")
);
store.removeAll(filter);// Remove documents marked as archived or deleted
Filter filter = metadataKey("status").isIn(
Arrays.asList("archived", "deleted", "obsolete")
);
store.removeAll(filter);// Documents from last 2 years with high priority
Filter filter = new And(
metadataKey("year").isGreaterThanOrEqualTo(2022),
metadataKey("priority").isGreaterThanOrEqualTo(7)
);// Exclude draft, archived, and deleted content
Filter filter = new Not(
metadataKey("status").isIn(
Arrays.asList("draft", "archived", "deleted")
)
);// Specific category with minimum quality
Filter filter = new And(
metadataKey("category").isEqualTo("tutorial"),
metadataKey("rating").isGreaterThanOrEqualTo(4.0)
);// Documents from multiple authors
Filter filter = metadataKey("author").isIn(
Arrays.asList("Alice", "Bob", "Carol")
);// Documents containing keyword in specific category
Filter filter = new And(
metadataKey("category").isEqualTo("technical"),
metadataKey("content").containsString("algorithm")
);// Production data only
Filter filter = new And(
new Not(metadataKey("environment").isEqualTo("test")),
new Not(metadataKey("tags").containsString("test"))
);import dev.langchain4j.data.document.Metadata;
import java.util.UUID;
Metadata metadata = new Metadata();
// String - Full support for all filter types
metadata.put("category", "documentation");
metadata.put("author", "John Doe");
// Integer - Supports equality, comparison, in/not-in
metadata.put("count", 42);
metadata.put("priority", 5);
// Long - Supports equality, comparison, in/not-in
metadata.put("timestamp", 1234567890L);
metadata.put("fileSize", 1024L);
// Double - Supports comparison filters
metadata.put("score", 9.5);
metadata.put("rating", 4.8);
// Float - Supports comparison filters
metadata.put("confidence", 0.95f);
metadata.put("weight", 1.5f);
// UUID - Supports equality and in/not-in
metadata.put("documentId", UUID.randomUUID());The library automatically converts LangChain4j filters to Qdrant filter format:
Match conditionMust Not with Match conditionRange with gtRange with gteRange with ltRange with lteMatch Any conditionMust Not with Match Any conditionMatch Text conditionMust conditionsShould conditionsMust Not conditionsThe following will throw UnsupportedOperationException:
ContainsString for substring matching)The following metadata types are not supported:
// Instead of Boolean
metadata.put("isActive", true); // NOT SUPPORTED
// Use Integer
metadata.put("isActive", 1); // 1 for true, 0 for false
Filter filter = metadataKey("isActive").isEqualTo(1);
// Instead of null checks
// Add metadata only when value exists
if (value != null) {
metadata.put("optionalField", value);
}More selective filters (matching fewer documents) perform better:
// Good: Selective filter
Filter selective = metadataKey("documentId").isEqualTo(specificUUID);
// Less optimal: Broad filter
Filter broad = metadataKey("category").isNotEqualTo("archived");Qdrant automatically indexes metadata fields. Equality and range filters are optimized:
// Optimized filters
metadataKey("category").isEqualTo("tech");
metadataKey("year").isGreaterThan(2020);
metadataKey("status").isIn(Arrays.asList("active", "pending"));
// Less optimized
metadataKey("content").containsString("keyword"); // Text searchSimpler filters generally perform better:
// Simple AND (good performance)
new And(
metadataKey("category").isEqualTo("tech"),
metadataKey("year").isGreaterThan(2020)
);
// Complex nested logic (slower)
new Or(
new And(filter1, filter2),
new And(
new Not(filter3),
new Or(filter4, filter5)
)
);// Test filter before using in production
Filter testFilter = metadataKey("category").isEqualTo("test");
// Add test document
Metadata metadata = new Metadata();
metadata.put("category", "test");
TextSegment segment = TextSegment.from("Test content", metadata);
String id = store.add(embedding, segment);
// Verify filter works
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(10)
.filter(testFilter)
.build();
EmbeddingSearchResult<TextSegment> results = store.search(request);
System.out.println("Found " + results.matches().size() + " matches");
// Cleanup
store.remove(id);// After adding, verify metadata is stored correctly
String id = store.add(embedding, segment);
// Search and check metadata
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(1)
.build();
EmbeddingSearchResult<TextSegment> results = store.search(request);
for (EmbeddingMatch<TextSegment> match : results.matches()) {
if (match.embeddingId().equals(id)) {
System.out.println("Metadata: " + match.embedded().metadata());
}
}Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-qdrant@1.11.0