Chunking Strategies
Many RAG docs describe chunking as "cutting a long document into a few pieces."
In AI4J, that framing is too thin. From the source, chunking determines far more than text length — it also determines:
- whether hit boundaries are stable
- whether
documentId + chunkIndexis reusable - whether citations can trace back to the correct passage
- whether metadata can flow all the way through to retrieval results
So what this page actually covers is: what AI4J's current default chunker does, and what it deliberately does not do for you.
1. Where the source entry points are
The four most important entry points today are:
rag/ingestion/Chunker.javarag/ingestion/RecursiveTextChunker.javarag/RagChunk.javadocument/RecursiveCharacterTextSplitter
The default implementation is:
public class RecursiveTextChunker implements Chunker
Behind it is not a semantic splitter; it is a wrapper layer over a character-based recursive splitter.
2. The Chunker contract is deliberately thin
The interface signature is:
List<RagChunk> chunk(RagDocument document, String content)
This shows AI4J's design intent at the chunking layer is unambiguous:
- give you a document object
- give you a piece of raw text
- you return a list of
RagChunk
It does not mandate:
- a PDF parsing strategy
- a Markdown structure preservation strategy
- a heading detection strategy
- a table-specific splitting strategy
In other words, the current chunking layer is an open contract, not a product layer with "enough document intelligence built in."
3. What the default RecursiveTextChunker actually does
The default implementation is very direct:
- If
contentis empty, return an empty list - Call
splitter.splitText(content)to obtain a number of text fragments - Generate a
RagChunkper fragment - Populate only:
documentIdcontentchunkIndex
The most important fact here is:
The default chunker does not automatically populate chunkId, pageNumber, sectionTitle, or metadata.
Yet RagChunk explicitly supports these fields:
chunkIddocumentIdcontentchunkIndexpageNumbersectionTitlemetadata
This shows the default implementation is positioned as "a text-splitting base component that runs end to end," not "a complete semantic-structure splitter."
4. Why the default strategy is sufficient, but far from complete
The default RecursiveTextChunker has these strengths:
- simple
- stable
- no extra model dependencies
- high reuse from the existing
RecursiveCharacterTextSplitter
But its boundaries are equally clear:
- it splits by text fragment, not by business structure
- it does not understand heading hierarchy
- it does not know whether paragraphs belong to the same section
- it does not know how to preserve boundaries for tables, code blocks, FAQs, or configuration blocks
- it does not auto-produce a reusable chunk primary key
So if your corpus is:
- API documentation
- regulations
- contracts
- academic papers
- multi-page PDFs
the default splitting is usually fine as a starting point, not as a final solution.
5. Why chunkIndex matters more than many people think
In AI4J's current implementation, chunkIndex is not just a display field.
Several downstream stages may rely on it:
HybridRetriever's dedup key may fall back todocumentId + chunkIndexRagHitSupport.stableKey(...)also useschunkIndex- during citation / trace troubleshooting, it is often used to locate "which chunk of the document this is"
This implies:
Once chunk order becomes unstable, downstream retrieval deduplication, citation positioning, and evaluation comparison all become brittle.
So when customizing a chunker, a robust principle is:
- when document content does not change, keep
chunkIndexstable - when re-running ingest, avoid letting chunk boundaries drift frequently
6. Why the default implementation does not generate chunkId
From the current source, the default chunker does not generate a chunkId. This is deliberately deferring an important decision to the business layer:
- whether to use
documentId + chunkIndex - whether to use a hash
- whether to encode page, section, or dataset into it
This is not an oversight; it is a boundary choice.
Because once the framework forces a chunkId definition on your behalf, everything downstream:
- vector store primary keys
- overwrite-on-write
- incremental updates
- deduplication strategy
gets locked in prematurely.
So AI4J is effectively saying:
"I give you the RagChunk structure, but the chunk primary key strategy is yours to decide."
7. Why chunking directly governs citation quality
When DefaultRagContextAssembler generates a citation, it uses the final hit RagHit.content directly as the snippet.
This means citation quality depends first on:
- whether the chunk is complete enough
- whether it happens to preserve the context the answer needs
- whether it has not stitched two unrelated passages together
If the chunk is too fragmented:
- the snippet loses semantics
- the answer may be missing its causal prelude
If the chunk is too large:
- the snippet becomes too long
- irrelevant context dilutes recall quality
- the model reads more noise
So citation quality is not decided by the citation subsystem alone — a large part of it is decided upstream by chunking.
8. The most common pitfalls in the current default chunking
8.1 Splitting text only, without populating metadata
DenseRetriever later tries to recover the following from metadata:
documentIdsourceNamesourcePathsourceUripageNumbersectionTitlechunkIndex
If this information is not populated during ingest, downstream retrieval hits and citations become noticeably "blind."
8.2 Tuning chunk size by token cost alone
Many people stare only at context cost when tuning chunkSize, ignoring:
- query hit granularity
- section completeness
- table/list fidelity
- rerank input quality
This makes the chunk look token-cheap while actual recall quality drops.
8.3 Letting chunk boundaries drift on every corpus rebuild
Once boundaries drift, comparing historical evaluations against production issues becomes hard — especially once you already have traces, citations, and offline evaluations.
9. When you should implement Chunker yourself
For these scenarios, customizing directly is recommended:
- you need to split by heading hierarchy
- you need to preserve page numbers
- you need to push section information into metadata
- you need a stable
chunkId - you are handling structured documents rather than plain text
A dependable custom chunker should at least consider:
- whether chunk boundaries are stable
- whether
documentIdis traceable - whether
pageNumber/sectionTitlecan be backfilled - whether the metadata is rich enough to support downstream citation / rerank / trace
10. The most robust design suggestions given AI4J's current implementation
If you are building production-grade RAG on this framework today, the most robust advice for the chunking layer is:
- First run the pipeline end to end with the default
RecursiveTextChunker - Pin down your primary key and metadata conventions
- Then switch to a custom
Chunkerbased on document type
Do not shove all the complexity into the retriever or reranker upfront. Because the root cause of many "inaccurate retrieval" issues is actually settled here, at chunking time.
11. The conclusion worth remembering from this page
The essence of AI4J's current default chunking is:
- use
RecursiveCharacterTextSplitterfor recursive text splitting - produce the most basic
RagChunk - populate only
documentId,content,chunkIndexby default
It solves "cut text into ingestable blocks"; it does not solve for you:
- structural semantic preservation
- a stable primary key
- page-number and section metadata
- production-grade citation friendliness
So chunking in AI4J is not a minor detail — it is the upstream switch for the entire RAG quality chain.