LangChain4j Text Splitters
Chunk documents effectively in LangChain4j: the recursive splitter, chunk size and overlap tuning, splitting code and markdown, and why chunking is the highest-impact decision in RAG.
On this page
Chunking is the highest-leverage decision in a RAG system and the one people spend the least time on. The chunk is the unit of retrieval — if the answer is split across two chunks, or buried in a chunk full of unrelated text, no embedding model or prompt can recover it. This tutorial covers LangChain4j's splitters and how to tune them.
Key Takeaways
- The chunk is the unit of retrieval; chunking caps what your system can ever find.
- The recursive splitter respects natural boundaries — the right default.
- Chunk size trades precision against context; overlap prevents boundary cuts.
- Tune against a real question set, not intuition. Different content wants different sizes.
The recursive splitter
The default choice. It splits on the largest natural boundary that keeps chunks under the target size — paragraphs first, then sentences, then words.
// 500-token chunks, 100-token overlap. A sensible starting point for prose.
DocumentSplitter splitter = DocumentSplitters.recursive(500, 100);
List<TextSegment> segments = splitter.split(document);Because it prefers paragraph and sentence boundaries, related sentences stay together far more often than with a blind fixed-length cut.
Chunk size: the core trade-off
// Small chunks: precise matches, but a chunk may lack the context that makes
// it meaningful. Good for FAQ-style content with self-contained answers.
DocumentSplitters.recursive(200, 40);
// Large chunks: rich context, but the relevant sentence is diluted by
// surrounding text, weakening the match. Good for narrative documents.
DocumentSplitters.recursive(800, 150);Content-specific splitting
Different content has different natural boundaries.
| Content | Approach | Why |
|---|---|---|
| Prose | Recursive, 300-600 tokens | Paragraph boundaries carry meaning |
| Reference / API docs | Recursive, 500-800 tokens | Keep a whole entry together |
| Source code | Split by function or class | Never split mid-function |
| Markdown | Split on headings | Headings are semantic boundaries |
| Tables / CSV | Whole rows with the header | A row without its header is meaningless |
// You can compose splitters for finer control.
DocumentSplitter splitter = DocumentSplitters.recursive(
512, // max segment size in tokens
64, // overlap
new OpenAiTokenizer()); // count tokens accurately for this modelCounting tokens correctly
Chunk sizes are in tokens, and tokenisation differs by model. Pass the right tokenizer so your target sizes are accurate:
Tokenizer tokenizer = new OpenAiTokenizer("gpt-4o-mini");
DocumentSplitter splitter = DocumentSplitters.recursive(500, 100, tokenizer);Without the correct tokenizer, "500 tokens" is an estimate that can be off by enough to matter for context-window budgeting. See tokenization and context windows.
Metadata survives splitting
Each segment inherits the document's metadata, so filtering and citation still work at chunk level:
List<TextSegment> segments = splitter.split(document);
// Each segment carries the parent document's metadata (sourceId, tenantId, ...)
// plus its position, so you can cite "chunk 3 of handbook.pdf".
segments.forEach(s -> System.out.println(s.metadata().getString("sourceId")));Tuning against a question set
The only reliable way to choose chunk parameters is to measure retrieval quality on real questions:
// For each candidate configuration, ingest, then check: for a fixed set of
// questions, is the correct passage in the top-k results?
for (var config : List.of(
DocumentSplitters.recursive(300, 60),
DocumentSplitters.recursive(500, 100),
DocumentSplitters.recursive(800, 150))) {
reingest(config);
double hitRate = evaluateRetrieval(goldenQuestions);
log.info("config={} hitRate={}", config, hitRate);
}Pick the configuration with the best hit rate on your content and your questions. This measured approach beats any rule of thumb. See testing AI applications.
Next
Frequently Asked Questions
What chunk size should I use for RAG?
What does chunk overlap do?
What is the recursive splitter in LangChain4j?
Why is chunking the most important part of RAG?
Related tutorials
- LangChain4j Document LoadersLoad documents into LangChain4j from files, URLs, S3, GitHub and more, and parse PDF, DOCX and HTML with Apache Tika — the ingestion front-end for any RAG pipeline in Java.
- LangChain4j Embedding ModelsConfigure embedding models in LangChain4j: hosted models like OpenAI and Cohere, free in-process ONNX models, dimension matching, and choosing an embedding model for RAG.
- LangChain4j Chains & CompositionCompose multi-step LLM workflows in LangChain4j: sequential chains, routing by classification, and building custom chains from AI Services — when to chain and when a single call suffices.
- LangChain4j Embedding StoresStore and search vectors in LangChain4j: the in-memory store for tests, PgVector for production, Redis and Elasticsearch, plus metadata filtering and picking the right store.