CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-dev-langchain4j--langchain4j-easy-rag

Zero-configuration RAG package that bundles document parsing, embedding, and splitting for easy Retrieval-Augmented Generation in Java applications

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

troubleshooting.mddocs/

Troubleshooting

Common issues and solutions when using easy-rag.

SPI Configuration Issues

Multiple DocumentSplitterFactory Implementations

Error:

IllegalStateException: Multiple implementations of DocumentSplitterFactory found

Cause: Multiple JARs on classpath provide DocumentSplitterFactory SPI implementations.

Example scenario:

  • easy-rag provides RecursiveDocumentSplitterFactory
  • Another library also provides a DocumentSplitterFactory
  • Framework cannot choose automatically

Solution 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>

Multiple EmbeddingModel Implementations

Error:

IllegalStateException: Multiple implementations of EmbeddingModelFactory found

Cause: 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();

No DocumentSplitter Implementation Found

Error:

IllegalStateException: No DocumentSplitterFactory implementation found

Cause: 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();

Memory Issues

OutOfMemoryError During Ingestion

Symptom:

java.lang.OutOfMemoryError: Java heap space

Causes:

  • Processing very large documents
  • Loading too many documents at once
  • Using InMemoryEmbeddingStore with large datasets
  • Insufficient JVM heap size

Solution 1: Increase JVM heap

java -Xmx4g -jar your-app.jar

Solution 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(...);

InMemoryEmbeddingStore Too Large

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 ≈ 2GB

Solution 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);

Document Parsing Issues

Unsupported Document Format

Symptom: Parser fails or returns empty content

Error:

TikaException: Unsupported file type

Cause: 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 documentation

Solution 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);

Corrupted or Malformed Documents

Symptom: Parser exception or partial content

Error:

TikaException: Failed to parse document

Solution: 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);

Password-Protected Documents

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.pdf

Solution 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 implementation

Embedding Issues

Slow Embedding Generation

Symptom: Ingestion takes very long time

Factors:

  • Number of documents/segments
  • CPU cores available
  • Quantized model slower than API-based
  • Model runs in-process (CPU-bound)

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));

Poor Retrieval Quality

Symptom: Retrieved documents not relevant to queries

Possible causes:

  1. Chunk size too large or too small
  2. Queries and documents use different terminology
  3. Min score threshold too high
  4. Insufficient number of results

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);

Retrieval Issues

No Results Returned

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");
}

Retrieval Returns Wrong Content

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:

  • Re-ingest with better chunking strategy
  • Use higher quality embedding model
  • Add query preprocessing
  • Increase max results to see more candidates

Serialization Issues

Cannot Deserialize Embedding Store

Error:

JsonParseException: Cannot deserialize embedding store

Cause 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");

Large Serialized File

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 database

Performance Issues

Ingestion Too Slow

Problem: 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 embeddings

Solution 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);
    });

Retrieval Too Slow

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();

Dependency Conflicts

Tika Version Conflict

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>

ONNX Runtime Conflict

Error:

UnsatisfiedLinkError: ONNX Runtime native library

Cause: 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();

Content Quality Issues

Retrieved Content Not Useful

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);

Duplicate or Near-Duplicate Content

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 threshold

Solution 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;
};

Integration Issues

AiServices Builder Errors

Error:

IllegalArgumentException: chatModel is required

Cause: Missing required configuration

Solution: Ensure all required fields set

Assistant assistant = AiServices.builder(Assistant.class)
    .chatModel(chatModel)           // Required
    .contentRetriever(retriever)     // Required for RAG
    .build();

ContentRetriever Not Used

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();

API Key Issues

Missing API Key

Error:

IllegalArgumentException: apiKey is required

Solution:

// 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();

Rate Limit Errors

Error:

RateLimitException: Rate limit exceeded

Solution: 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;
        }
    }
}

Getting Help

If you encounter issues not covered here:

  1. Check store state: Verify embeddings exist and are accessible
  2. Test components individually: Test parsing, splitting, embedding separately
  3. Enable logging: Add SLF4J logging to see internal operations
  4. Simplify: Remove customizations and test with defaults
  5. Verify dependencies: Ensure all transitive dependencies resolved correctly

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");

Related Documentation

  • Configuration - Configuration options
  • Architecture - How components work together
  • Examples - Working examples
  • Reference - External resources

Install with Tessl CLI

npx tessl i tessl/maven-dev-langchain4j--langchain4j-easy-rag@1.11.0

docs

api-document-loading.md

api-ingestion.md

api-retrieval.md

api-types-chat.md

api-types-core.md

api-types-storage.md

architecture.md

configuration.md

examples.md

index.md

quickstart.md

reference.md

troubleshooting.md

tile.json