Reading hits back
|
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. |
A TopDocs gives back doc ids and scores, nothing else. To show results you fetch the stored copy of
each document, and to sort, group or compute over them you read the columnar (doc-values) copy.
Lucene 10 exposes both through small per-reader accessor objects obtained from the searcher.
Stored fields
searcher.storedFields() returns a StoredFields bound to the current reader.
document(docId) materialises a Document from the stored fields written at index time. The old
IndexSearcher.doc(int) / IndexReader.document(int) methods are deprecated — use the accessor.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/index/StoredFields.html
import org.apache.lucene.index.StoredFields;
import org.apache.lucene.document.Document;
StoredFields storedFields = searcher.storedFields(); // not thread-safe: one per thread
for (ScoreDoc sd : hits.scoreDocs) {
Document doc = storedFields.document(sd.doc);
String title = doc.get("title");
String url = doc.get("url");
}
Selective loading with StoredFieldVisitor
Loading only the fields a result page displays avoids decompressing the rest of the stored block. The
Set overload is the shortcut; a StoredFieldVisitor gives full control, including aborting once the
wanted fields are in hand.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/index/StoredFieldVisitor.html
import java.util.Set;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.StoredFieldVisitor;
Document slim = storedFields.document(docId, Set.of("title", "url"));
class TitleOnly extends StoredFieldVisitor {
String title;
@Override public Status needsField(FieldInfo fi) {
return fi.name.equals("title") ? Status.YES : Status.STOP; // fields are visited in order
}
@Override public void stringField(FieldInfo fi, String value) { title = value; }
}
TitleOnly v = new TitleOnly();
storedFields.document(docId, v);
StoredFieldVisitor has a typed callback per stored type (stringField, intField, longField,
floatField, doubleField, binaryField); needsField returns YES, NO or STOP.
Doc values for computed columns
Stored fields are the display copy; doc values are the per-segment columnar copy used for sorting,
faceting and any custom per-hit computation. Read them by segment, mapping a global doc id to the
segment-local id with leaf.docBase.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/index/DocValues.html
import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.NumericDocValues;
for (LeafReaderContext leaf : searcher.getIndexReader().leaves()) {
NumericDocValues price = DocValues.getNumeric(leaf.reader(), "price");
int local = globalDocId - leaf.docBase;
if (local >= 0 && local < leaf.reader().maxDoc() && price.advanceExact(local)) {
long value = price.longValue();
}
}
Field types and how to index the doc-values variant are on Documents & fields.
Term vectors
If a field was indexed with term vectors, searcher.termVectors().get(docId, field) returns a
per-document Terms — the terms, frequencies and (optionally) positions/offsets for that one
document. It is the input to the fast highlighters and to "more like this".
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/index/TermVectors.html
import org.apache.lucene.index.TermVectors;
import org.apache.lucene.index.Terms;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.util.BytesRef;
TermVectors termVectors = searcher.termVectors();
Terms tv = termVectors.get(docId, "body"); // null if the field has no term vectors
if (tv != null) {
TermsEnum te = tv.iterator();
BytesRef term;
while ((term = te.next()) != null) {
long freq = te.totalTermFreq();
}
}
Paging with searchAfter
searchAfter(ScoreDoc after, Query, n) resumes ranking just past a hit from the previous page,
avoiding the growing cost of search(query, pageStart + pageSize). With a sort, after must be the
FieldDoc returned for the last hit so its sort values travel with it.
// https://lucene.apache.org/core/10_0_0/core/org/apache/lucene/search/IndexSearcher.html#searchAfter(org.apache.lucene.search.ScoreDoc,org.apache.lucene.search.Query,int)
int PAGE = 20;
TopDocs page1 = searcher.search(query, PAGE);
ScoreDoc last = page1.scoreDocs[page1.scoreDocs.length - 1];
TopDocs page2 = searcher.searchAfter(last, query, PAGE);
// Sorted paging: cast the cursor to FieldDoc and pass the same Sort.
TopFieldDocs s1 = (TopFieldDocs) searcher.search(query, PAGE, sort);
FieldDoc sLast = (FieldDoc) s1.scoreDocs[s1.scoreDocs.length - 1];
TopFieldDocs s2 = (TopFieldDocs) searcher.searchAfter(sLast, query, PAGE, sort);
A stable searcher across pages
Every page of one result set must run against the same IndexSearcher. Doc ids are only stable
within a reader generation: after a merge or a reopen they are renumbered, so a cursor captured
against the old reader points at the wrong documents and counts drift. Hold the searcher for the
pagination session — keep the IndexSearcher acquired from SearcherManager (see
Near-real-time search) until the user stops paging,
or cache it under a short-lived search token — and release it when done. Reopen the reader only
when the user starts a fresh query, or deliberately to surface new documents, accepting that deep
cursors are then invalidated. The equivalent trade-off in a distributed engine — search_after
plus a point-in-time reader — is covered in
Elasticsearch: Search API & pagination.
Related pages
-
Near-real-time search —
SearcherManagerand holding a searcher. -
Documents & fields — which fields are stored, doc-valued or term-vectored.
-
Collectors & concurrent search — producing the
TopDocsthese APIs read from. -
Function scoring, expressions & sorting —
FieldDoccursors for sorted paging. -
Highlighting, suggesters & more — term vectors as highlighter input.
-
Elasticsearch: Search API & pagination —
search_afterand point-in-time.