Analysis pipeline: Analyzer, TokenStream & attributes
|
This section documents the current Apache Lucene 10.x line — Lucene 10 requires Java 21 — as published
at the Apache Lucene documentation and Javadoc, which is the reference these
pages are written and verified against. No specific patch version is pinned; examples target This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as Lucene iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
An Analyzer is the factory that produces the TokenStream Lucene consumes when it indexes a text
field and, by default, when it parses a query. The stream is assembled from an optional chain of
character filters, exactly one tokenizer, and an optional chain of token filters, and each token
it emits carries a bundle of attributes — the term text, its character offsets, its position. This
page covers how the pipeline is wired, its lifecycle, and the attribute API.
Analyzer: createComponents and normalize
A concrete Analyzer implements createComponents(String fieldName), returning a
TokenStreamComponents that bundles the source Tokenizer with the (possibly wrapped) final
TokenStream. Lucene calls this once per field per thread and then reuses the components for
subsequent values of that field, resetting the chain each time. normalize(String, TokenStream) is
the lighter partner path: it applies only the analyzer’s character-level filters (lowercasing,
ASCII folding) and is used for TermQuery, PrefixQuery and sorting, where the input is a single
token that must not be split.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/analysis/Analyzer.html
Analyzer analyzer = new Analyzer() {
@Override
protected TokenStreamComponents createComponents(String fieldName) {
Tokenizer source = new StandardTokenizer();
TokenStream result = new LowerCaseFilter(source);
result = new StopFilter(result, EnglishAnalyzer.getDefaultStopSet());
return new TokenStreamComponents(source, result);
}
@Override
protected TokenStream normalize(String fieldName, TokenStream in) {
return new LowerCaseFilter(in); // no tokenization: keep it one token
}
};
The CharFilter → Tokenizer → TokenFilter chain
The three stages always run in this order:
-
Character filters (
CharFilter, zero or more) — rewrite the raw character stream before tokenization while tracking a correction map so final offsets still point into the original text. Example:HTMLStripCharFilter,MappingCharFilter. -
Tokenizer (
Tokenizer, exactly one) — consume the characterReaderand break it into tokens, recording each token’s start/end offset and position. Example:StandardTokenizer,WhitespaceTokenizer,KeywordTokenizer. -
Token filters (
TokenFilter, zero or more) — add, drop or rewrite tokens in order:LowerCaseFilter,StopFilter,SynonymGraphFilter, a stemmer.
Token filters & recipes and
Built-in analyzers & CustomAnalyzer
cover the concrete components; the package overview is at
the
org.apache.lucene.analysis package summary.
TokenStream lifecycle and reuse
A TokenStream is a strict state machine. A consumer must call the four methods in order, exactly
once per pass:
-
reset()— rewind to the start before the firstincrementToken(). -
incrementToken()— advance to the next token, populating the shared attributes; returnsfalseat end of stream. -
end()— called after the last token to set end-of-stream state such as the final offset. -
close()— release the input; the stream can be re-reset()and used again for the next value.
Because Analyzer caches TokenStreamComponents per thread, the same stream objects are reused
across every value of a field — which is why reset() and end() exist and why a custom TokenFilter
must reset its own state in reset().
The attribute API
Tokens are not objects. A TokenStream extends AttributeSource: attribute instances are created
once with addAttribute(…), shared by the whole chain, and mutated in place on each
incrementToken(). The consumer reads whichever attributes it needs after each call.
| Attribute | Carries |
|---|---|
|
The token’s text (as a mutable char buffer). |
|
Start and end character offsets in the original input — what the |
|
Gap to the previous token: |
|
How many positions the token spans — |
|
The tokenizer’s classification, e.g. |
|
An arbitrary |
|
Marks a token as a keyword so downstream stemmers leave it untouched. |
Positions and offsets are what make later features possible: PhraseQuery and the
interval and span queries match on positions,
while offsets let the UnifiedHighlighter map a matched term back to a span of source characters
without re-analyzing.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/analysis/tokenattributes/package-summary.html
Analyzer analyzer = new StandardAnalyzer();
try (TokenStream ts = analyzer.tokenStream("body", "The 2 QUICK brown-foxes jumped")) {
var term = ts.addAttribute(CharTermAttribute.class);
var offset = ts.addAttribute(OffsetAttribute.class);
var posInc = ts.addAttribute(PositionIncrementAttribute.class);
int position = 0;
ts.reset();
while (ts.incrementToken()) {
position += posInc.getPositionIncrement();
System.out.printf("term=%-8s pos=%d offset=[%d,%d]%n",
term, position, offset.startOffset(), offset.endOffset());
}
ts.end();
}
// term=the pos=1 offset=[0,3]
// term=2 pos=2 offset=[4,5]
// term=quick pos=3 offset=[6,11]
// term=brown pos=4 offset=[12,17]
// term=foxes pos=5 offset=[18,23]
// term=jumped pos=6 offset=[24,30]
Related pages
-
Built-in analyzers & CustomAnalyzer — the ready-made analyzers and building one from factory names.
-
Token filters & recipes — the concrete token filters and how to combine them.
-
Language analysis — stemming and per-language chains.
-
Highlighting, suggesters & more — how the
UnifiedHighlighteruses offsets. -
Elasticsearch: text analysis — the same chain, configured through mappings.
-
Solr: text analysis — the same chain, configured through the schema.
-
TokenStream Javadoc — the full lifecycle contract.