Zero-configuration RAG package that bundles document parsing, embedding, and splitting for easy Retrieval-Augmented Generation in Java applications
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Common issues and solutions when using easy-rag.
Error:
IllegalStateException: Multiple implementations of DocumentSplitterFactory foundCause: Multiple JARs on classpath provide DocumentSplitterFactory SPI implementations.
Example scenario:
RecursiveDocumentSplitterFactoryDocumentSplitterFactorySolution 1: Explicitly configure preferred splitter
// Choose which splitter to use
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.documentSplitter(yourPreferredSplitter)
.embeddingStore(store)
.build();Solution 2: Remove unwanted dependency from classpath
<!-- Exclude conflicting splitter -->
<dependency>
<groupId>some.library</groupId>
<artifactId>conflicting-lib</artifactId>
<exclusions>
<exclusion>
<groupId>dev.langchain4j</groupId>
<artifactId>conflicting-splitter</artifactId>
</exclusion>
</exclusions>
</dependency>Error:
IllegalStateException: Multiple implementations of EmbeddingModelFactory foundCause: Multiple embedding model providers on classpath.
Solution: Explicitly configure embedding model
import dev.langchain4j.model.embedding.bge.small.en.v15.BgeSmallEnV15QuantizedEmbeddingModel;
EmbeddingModel model = new BgeSmallEnV15QuantizedEmbeddingModel();
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingModel(model)
.embeddingStore(store)
.build();Error:
IllegalStateException: No DocumentSplitterFactory implementation foundCause: easy-rag not on classpath or excluded.
Solution: Ensure easy-rag dependency is included
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
<version>1.11.0-beta19</version>
</dependency>Alternative: Explicitly provide splitter
import dev.langchain4j.data.document.splitter.DocumentSplitters;
DocumentSplitter splitter = DocumentSplitters.recursive(300, 30);
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.documentSplitter(splitter)
.embeddingStore(store)
.build();Symptom:
java.lang.OutOfMemoryError: Java heap spaceCauses:
Solution 1: Increase JVM heap
java -Xmx4g -jar your-app.jarSolution 2: Process documents in batches
List<Document> allDocs = loadAllDocuments();
int batchSize = 50;
for (int i = 0; i < allDocs.size(); i += batchSize) {
int end = Math.min(i + batchSize, allDocs.size());
List<Document> batch = allDocs.subList(i, end);
ingestor.ingest(batch);
// Explicit GC hint (optional)
System.gc();
}Solution 3: Process files one at a time
import java.nio.file.Files;
Path docsDir = Paths.get("documents");
Files.walk(docsDir)
.filter(Files::isRegularFile)
.forEach(path -> {
Document doc = FileSystemDocumentLoader.loadDocument(path);
ingestor.ingest(doc);
// Document eligible for GC after ingestion
});Solution 4: Use persistent embedding store
// Instead of InMemoryEmbeddingStore, use database-backed store
// Example: Pinecone, Weaviate, Qdrant, etc.
EmbeddingStore<TextSegment> store = new PineconeEmbeddingStore(...);Symptom: High memory usage or OOM when loading from file
Cause: Embedding store contains too many embeddings for available memory
Estimate memory usage:
Memory per embedding ≈ (384 dimensions × 4 bytes) + object overhead ≈ 2KB
100,000 embeddings ≈ 200MB
1,000,000 embeddings ≈ 2GBSolution 1: Use external vector database
<!-- Use persistent vector database -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-pinecone</artifactId>
</dependency>Solution 2: Split into multiple stores
// Separate stores by category
InMemoryEmbeddingStore<TextSegment> store1 = loadCategory1();
InMemoryEmbeddingStore<TextSegment> store2 = loadCategory2();
// Use appropriate store based on query
ContentRetriever retriever = selectRetriever(query);Symptom: Parser fails or returns empty content
Error:
TikaException: Unsupported file typeCause: File format not supported by Apache Tika
Solution 1: Check if format is supported
// Apache Tika supports 200+ formats including:
// PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, ODT, RTF, TXT, HTML, XML, CSV
// For unsupported formats, check Tika documentationSolution 2: Convert to supported format
# Example: Convert Pages to PDF
# (use system tools or libraries)Solution 3: Manual text extraction
// Extract text manually for unsupported formats
String text = extractTextFromCustomFormat(filePath);
Document document = Document.from(text);
document.metadata().put("source", filePath.toString());
ingestor.ingest(document);Symptom: Parser exception or partial content
Error:
TikaException: Failed to parse documentSolution: Handle errors gracefully
import java.nio.file.Files;
import java.util.ArrayList;
List<Document> documents = new ArrayList<>();
List<Path> failedFiles = new ArrayList<>();
Files.walk(Paths.get("documents"))
.filter(Files::isRegularFile)
.forEach(path -> {
try {
Document doc = FileSystemDocumentLoader.loadDocument(path);
// Verify document has content
if (doc.text() != null && !doc.text().trim().isEmpty()) {
documents.add(doc);
} else {
System.err.println("Empty content: " + path);
failedFiles.add(path);
}
} catch (Exception e) {
System.err.println("Failed to parse " + path + ": " + e.getMessage());
failedFiles.add(path);
}
});
System.out.println("Loaded: " + documents.size());
System.out.println("Failed: " + failedFiles.size());
// Process successful documents
EmbeddingStoreIngestor.ingest(documents, store);Symptom: Parser cannot access content
Cause: Document is encrypted/password-protected
Solution 1: Remove password protection before processing
# Use tools like pdftk, LibreOffice, etc.
pdftk encrypted.pdf input_pw PASSWORD output unlocked.pdfSolution 2: Configure Tika with password (if supported)
import dev.langchain4j.data.document.parser.apache.tika.ApacheTikaDocumentParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.pdf.PDFParserConfig;
ParseContext context = new ParseContext();
PDFParserConfig config = new PDFParserConfig();
config.setUserPassword("password");
context.set(PDFParserConfig.class, config);
// Note: May require custom DocumentParser implementationSymptom: Ingestion takes very long time
Factors:
Solution 1: Monitor progress
List<Document> documents = loadDocuments();
System.out.println("Processing " + documents.size() + " documents...");
for (int i = 0; i < documents.size(); i++) {
ingestor.ingest(documents.get(i));
System.out.println("Progress: " + (i + 1) + "/" + documents.size());
}Solution 2: Use persistent store to avoid re-processing
// First time: Generate and save
EmbeddingStoreIngestor.ingest(documents, store);
store.serializeToFile("embeddings.json");
// Subsequent runs: Load from file
InMemoryEmbeddingStore<TextSegment> store =
InMemoryEmbeddingStore.fromFile("embeddings.json");Solution 3: Use API-based embedding model (faster)
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
// Much faster than in-process model
EmbeddingModel model = OpenAiEmbeddingModel.builder()
.apiKey(apiKey)
.modelName("text-embedding-3-small")
.build();
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.embeddingModel(model)
.embeddingStore(store)
.build();Solution 4: Parallel processing (with thread-safe store)
// Use parallel streams (ensure store is thread-safe)
documents.parallelStream()
.forEach(doc -> ingestor.ingest(doc));Symptom: Retrieved documents not relevant to queries
Possible causes:
Solution 1: Adjust chunk size
// Try smaller chunks for more precise retrieval
DocumentSplitter smallChunks = DocumentSplitters.recursive(200, 20);
// Or larger chunks for more context
DocumentSplitter largeChunks = DocumentSplitters.recursive(500, 50);Solution 2: Increase max results
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.maxResults(10) // More results to consider
.minScore(0.5) // Lower threshold
.build();Solution 3: Use better embedding model
// Upgrade to higher quality model
EmbeddingModel betterModel = OpenAiEmbeddingModel.builder()
.apiKey(apiKey)
.modelName("text-embedding-3-large") // 3072 dimensions
.build();Solution 4: Query transformation
// Preprocess queries for better matching
String improvedQuery = originalQuery
.toLowerCase()
.replaceAll("[^a-z0-9\\s]", "")
.trim();
String answer = assistant.chat(improvedQuery);Symptom: Retrieval returns empty list or very few results
Cause 1: Min score threshold too high
// Check your threshold
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.minScore(0.95) // Too high! Most matches filtered out
.build();Solution:
// Lower or remove threshold
builder.minScore(0.6); // More permissive
// Or no threshold
builder.minScore(0.0);Cause 2: Filter too restrictive
// Filter excludes all documents
Filter filter = metadataKey("category").isEqualTo("nonexistent");Solution: Verify filter logic and metadata
// Check what metadata exists in store
EmbeddingSearchResult<TextSegment> allResults = store.search(
EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(1)
.build()
);
if (!allResults.matches().isEmpty()) {
Metadata meta = allResults.matches().get(0).embedded().metadata();
System.out.println("Available metadata: " + meta.toMap());
}Cause 3: Empty embedding store
// Check store size
System.out.println("Store size: " + store.size());
if (store.isEmpty()) {
System.out.println("Store is empty - need to ingest documents first");
}Symptom: Retrieved content not related to query
Debug approach:
import dev.langchain4j.rag.content.Content;
import dev.langchain4j.rag.content.ContentMetadata;
// Manual retrieval to inspect results
Embedding queryEmbedding = embeddingModel.embed("test query").content();
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(10)
.minScore(0.0)
.build();
EmbeddingSearchResult<TextSegment> result = store.search(request);
// Inspect all results and scores
for (EmbeddingMatch<TextSegment> match : result.matches()) {
System.out.println("Score: " + match.score());
System.out.println("Text: " + match.embedded().text().substring(0, 100) + "...");
System.out.println("---");
}Common fixes:
Error:
JsonParseException: Cannot deserialize embedding storeCause 1: File corrupted or incompatible format
Solution: Regenerate embeddings
// Backup old file
Files.move(
Paths.get("embeddings.json"),
Paths.get("embeddings.json.backup")
);
// Regenerate
EmbeddingStore<TextSegment> newStore = new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor.ingest(documents, newStore);
newStore.serializeToFile("embeddings.json");Cause 2: Version mismatch
Solution: Regenerate with current version
// Cannot mix embeddings from different LangChain4j versions
// Delete old file and recreate
Files.deleteIfExists(Paths.get("embeddings.json"));
// Regenerate with current version
InMemoryEmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor.ingest(documents, store);
store.serializeToFile("embeddings.json");Symptom: embeddings.json is very large (>100MB)
Cause: Many embeddings in store
Solution 1: Use compressed storage
# Compress the JSON file
gzip embeddings.json
# Load and decompress in Java
import java.util.zip.GZIPInputStream;Solution 2: Use database-backed store
// Switch to vector database for production
EmbeddingStore<TextSegment> store = new PineconeEmbeddingStore(...);
// No need to serialize - data stored in databaseProblem: Takes too long to process documents
Benchmark:
import java.time.Instant;
import java.time.Duration;
Instant start = Instant.now();
IngestionResult result = ingestor.ingest(documents);
Duration duration = Duration.between(start, Instant.now());
System.out.println("Processed " + documents.size() + " documents in " +
duration.toSeconds() + " seconds");
System.out.println("Average: " +
(duration.toMillis() / documents.size()) + "ms per document");Solution 1: Use API-based embedding model (10-100x faster)
EmbeddingModel fastModel = OpenAiEmbeddingModel.builder()
.apiKey(apiKey)
.modelName("text-embedding-3-small")
.build();Solution 2: Process offline/batch
// Process documents as background job
// Save to persistent store
// Application loads pre-computed embeddingsSolution 3: Reduce chunk count
// Larger chunks = fewer embeddings
DocumentSplitter largerChunks = DocumentSplitters.recursive(500, 50);Solution 4: Optimize document loading
// Don't load all documents into memory at once
Files.walk(docsDir)
.filter(Files::isRegularFile)
.forEach(path -> {
Document doc = FileSystemDocumentLoader.loadDocument(path);
ingestor.ingest(doc);
});Problem: Queries take too long to return results
Cause 1: Large embedding store (InMemoryEmbeddingStore is linear search)
Solution: Use vector database with indexing
// Vector databases use approximate nearest neighbor search
// Much faster than linear search
EmbeddingStore<TextSegment> store = new WeaviateEmbeddingStore(...);Cause 2: Embedding query is slow
Solution: Cache embedding model instance
// Reuse model across queries (don't recreate)
EmbeddingModel model = createEmbeddingModel(); // Create once
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.embeddingModel(model) // Reuse same instance
.build();Error:
NoSuchMethodError: org.apache.tika...Cause: Another dependency brings different Tika version
Solution: Enforce easy-rag's Tika version
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>3.2.3</version>
</dependency>
</dependencies>
</dependencyManagement>Error:
UnsatisfiedLinkError: ONNX Runtime native libraryCause: ONNX Runtime conflict or missing native libraries
Solution 1: Ensure ONNX Runtime on classpath (should be transitive from easy-rag)
Solution 2: Use explicit embedding model
// Bypass ONNX by using API-based model
EmbeddingModel model = OpenAiEmbeddingModel.builder()
.apiKey(apiKey)
.modelName("text-embedding-3-small")
.build();Symptom: RAG responses don't use retrieved content effectively
Debug retrieval:
import dev.langchain4j.rag.content.Content;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.query.Query;
ContentRetriever retriever = EmbeddingStoreContentRetriever.from(store);
// Manually retrieve and inspect
List<Content> contents = retriever.retrieve(Query.from("test query"));
System.out.println("Retrieved " + contents.size() + " contents:");
for (Content content : contents) {
Double score = (Double) content.metadata().get(ContentMetadata.SCORE);
String text = content.textSegment().text();
System.out.println("\nScore: " + score);
System.out.println("Text preview: " + text.substring(0, Math.min(200, text.length())));
}Solution 1: Adjust chunk size and overlap
// Experiment with different sizes
DocumentSplitter splitter = DocumentSplitters.recursive(400, 60);Solution 2: Improve document metadata
// Add more context to segments
TextSegmentTransformer enricher = segment -> {
// Add parent document info
segment.metadata().put("doc_title", getDocumentTitle(segment));
segment.metadata().put("section", extractSection(segment));
return segment;
};Solution 3: Query preprocessing
// Expand or reformulate queries
String expandedQuery = originalQuery + " " + extractKeywords(originalQuery);
String answer = assistant.chat(expandedQuery);Symptom: Multiple similar chunks retrieved
Cause: Overlapping chunks or duplicate documents
Solution 1: Reduce overlap
DocumentSplitter noOverlap = DocumentSplitters.recursive(300, 0);Solution 2: Post-process results
// Deduplicate results by content similarity
List<Content> contents = retriever.retrieve(query);
List<Content> deduped = deduplicateContents(contents, 0.95); // 95% similarity thresholdSolution 3: Filter during loading
// Track processed documents to avoid duplicates
Set<String> processedFiles = new HashSet<>();
DocumentTransformer deduplicator = doc -> {
String fileHash = computeHash(doc.text());
if (processedFiles.contains(fileHash)) {
return null; // Skip duplicate
}
processedFiles.add(fileHash);
return doc;
};Error:
IllegalArgumentException: chatModel is requiredCause: Missing required configuration
Solution: Ensure all required fields set
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel) // Required
.contentRetriever(retriever) // Required for RAG
.build();Symptom: AI responses don't reference documents
Debug:
// Verify retriever returns results
ContentRetriever retriever = EmbeddingStoreContentRetriever.from(store);
List<Content> contents = retriever.retrieve(Query.from("test"));
System.out.println("Retriever returns " + contents.size() + " results");
if (contents.isEmpty()) {
System.out.println("Problem: retriever returns no content");
}Solution: Check retriever configuration
// Ensure store has content
System.out.println("Store size: " + store.size());
// Lower thresholds
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.maxResults(5)
.minScore(0.0) // No threshold for debugging
.build();Error:
IllegalArgumentException: apiKey is requiredSolution:
// Set environment variable
String apiKey = System.getenv("OPENAI_API_KEY");
if (apiKey == null || apiKey.isEmpty()) {
throw new IllegalStateException(
"OPENAI_API_KEY environment variable not set"
);
}
ChatModel model = OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-4o-mini")
.build();Error:
RateLimitException: Rate limit exceededSolution: Add retry logic or rate limiting
import java.time.Duration;
// Add timeout and handle errors
OpenAiChatModel model = OpenAiChatModel.builder()
.apiKey(apiKey)
.modelName("gpt-4o-mini")
.timeout(Duration.ofSeconds(60))
.build();
// Implement retry logic
int maxRetries = 3;
for (int i = 0; i < maxRetries; i++) {
try {
String response = assistant.chat(query);
return response;
} catch (RateLimitException e) {
if (i < maxRetries - 1) {
Thread.sleep(1000 * (i + 1)); // Exponential backoff
} else {
throw e;
}
}
}If you encounter issues not covered here:
Useful debug commands:
// Check embedding store
System.out.println("Store size: " + store.size());
System.out.println("Store empty: " + store.isEmpty());
// Check embedding model
EmbeddingModel model = getEmbeddingModel();
System.out.println("Model dimension: " + model.dimension());
System.out.println("Model name: " + model.modelName());
// Test retrieval
EmbeddingSearchResult<TextSegment> result = store.search(
EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding)
.maxResults(1)
.minScore(0.0)
.build()
);
System.out.println("Search returned " + result.matches().size() + " results");Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-easy-rag