All findings · Lucene finding · LFV-785 · memoryindex-nonindexed-field-reports-indexed

MemoryIndex reports fields that index no terms (doc values, points, vectors, stored only) as indexed with positions and norms

Put NumericDocValuesField("price", 42) into a MemoryIndex and its reader says price is indexed with positions and norms, while IndexWriter records it as not indexed (NONE). On 9.0.0 and later, copying that reader into an index with addIndexes then throws, or leaves an index that rejects the same document afterwards.

Summary

Priority
Low, rank 602MemoryIndex reporting term-less fields as indexed mostly fails loudly at addIndexes and needs a rare composition to persist a wrong schema.
Impact
crash
Since
6.0.0 (LUCENE-7091, 2016); the addIndexes exceptions since 9.0.0introduced 2016-03-15 · over 10 years
Affected
6.0.0 to 10.6.0 and main; exceptions from 9.0.0, a silently wrong schema on 6.x to 8.x
Status
documented
Found by
a bounded-exhaustive differential check of addIndexes from 10 reader kinds, then a JUnit

AI-assisted page: this finding was first signalled by a semi-formal check (an exact check of the real jar, or a modelling hypothesis that a tool then witnessed) and then checked with AI tools; the checks it went through, and what each found, are under Prior reports, history and checks. Not yet triaged by a human. Snapshot of 2026-11-06.

The finding in 30 seconds

A doc-values field such as price = 42 holds a value per document but no searchable terms. IndexWriter records it as not indexed; MemoryIndex claims it is indexed with positions.

Background: MemoryIndex, field metadata and addIndexes

  1. MemoryIndex indexes one document in memory so you can run queries against it. Its reader looks like a one-document index segment.
  2. Each field has a FieldInfo. Its index options say whether terms were indexed: NONE for a doc value, point, vector or stored-only field; DOCS_AND_FREQS_AND_POSITIONS for text with positions. Only indexed fields have norms.
  3. IndexWriter.addIndexes(CodecReader...) copies readers into an index; SlowCodecReaderWrapper.wrap adapts any reader. Since 9.0.0 the writer first checks every field's FieldInfo against the schema the index already has.
A document with a TextField title and a NumericDocValuesField price = 42. IndexWriter records price as index options NONE with no norms; MemoryIndex reports DOCS_AND_FREQS_AND_POSITIONS with norms although it holds no terms for price. Copying the MemoryIndex reader with addIndexes into an index holding the same document throws; copying it into an empty index makes a later addDocument of the same document throw.
The same price field as IndexWriter records it (NONE, no norms) and as MemoryIndex reports it (positions, norms), and the two addIndexes failures that follow. Select the still to open it at full size. Generated with AI, checked against the code.

The finding, step by step

  1. Build a MemoryIndex from one document: a TextField title and NumericDocValuesField("price", 42). price is only a doc value (for sorting or facets), so MemoryIndex stores no term for it.
  2. Ask the reader how price is indexed. You expect what IndexWriter records for the same document: NONE, no norms. You get DOCS_AND_FREQS_AND_POSITIONS with norms, and FieldInfos.getIndexedFields lists [price, title] instead of [title].
  3. On 9.0.0 or later, copy the reader with addIndexes(SlowCodecReaderWrapper.wrap(reader)) into an index that already holds this document. You expect 2 documents; you get IllegalArgumentException: cannot change field "price" from index options=NONE to inconsistent index options=DOCS_AND_FREQS_AND_POSITIONS.
  4. Copy it into an empty index instead: the copy succeeds but records price as indexed, so a later addDocument of the same document throws the reverse message, also after reopening the writer. 6.x to 8.x accept both steps silently.
  5. Why: MemoryIndex hard-codes positions as the index options of every field name and never reads the field type's own indexOptions(). Doc-values, point, vector and stored-only fields are all affected; search on the MemoryIndex is not.

Who is affected

Applications that copy a MemoryIndex reader into a real index with addIndexes(SlowCodecReaderWrapper.wrap(reader)), or rely on its FieldInfos, for documents with doc-values, point, vector or stored-only fields. Default settings; one field is enough. Search on the MemoryIndex is unaffected. StringField/KeywordField documents and MemoryIndex(storeOffsets=true) fail that copy anyway, by design.

Mechanism, terms and numbers783 words

MemoryIndex accepts fields that index no terms: doc values, points, vectors and stored-only values. For each such field name (when no instance of the name indexes terms), its reader builds a FieldInfo whose index options are hard-coded to DOCS_AND_FREQS_AND_POSITIONS (..._AND_OFFSETS with storeOffsets), so hasNorms() is true and getNormValues returns a norms iterator, although the field type says NONE, MemoryIndex stores no term, and IndexWriter records NONE with no norms for the same document. FieldInfo#getIndexOptions promises NONE "if the field is not indexed".

Search on the MemoryIndex is unaffected; the wrong metadata shows when the reader is copied into an index with IndexWriter.addIndexes(SlowCodecReaderWrapper.wrap(reader)). On 9.0.0 and later, next to a natively indexed copy of the document the writer's schema check throws, and into an empty index the copy succeeds but persists the wrong schema, after which ordinary addDocument calls with that field are rejected, in later writer sessions too, until the copied documents are deleted before they are merged with other documents and a new writer is opened, or the index is rebuilt. FieldExistsQuery on a stored-only or point-only field also matches on a MemoryIndex where a real segment throws.

The metadata has been wrong since 6.0.0, when MemoryIndex started accepting fields without terms; the exceptions appear from 9.0.0, when the writer's strict schema checks arrived. A fix in MemoryIndex (take NONE from the field type, switch to positions when a later instance of the name indexes terms, serve norms only when hasNorms(), and, as IndexWriter does, invert only fields whose type indexes something) passes the repro and the lucene/memory, lucene/monitor and lucene/highlighter test suites.

The mechanism (main 645d4fe14a20)

  1. Accepting the field. addField(IndexableField, Analyzer) calls getInfo(field.name(), field.fieldType()) first (MemoryIndex.java:622), so every field gets a FieldInfo. A null token stream with indexOptions() == NONE stores no term (:636-645); only the doc value, point, stored value or vector is kept (:647-679).
  2. One FieldInfo per name. getInfo builds the FieldInfo from the first instance of a name (:739-744); later instances only add point dimensions and the doc-values type (:745-755).
  3. The hard-coded options (createFieldInfo, :759-783):
   IndexOptions indexOptions =
       storeOffsets
           ? IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS
           : IndexOptions.DOCS_AND_FREQS_AND_POSITIONS;

fieldType.indexOptions() is never read. The other field-type settings come from the field type (term vectors, omitNorms, doc values, skip index, points, vectors: :767-768, :771-772, :775-780); payloads follow the MemoryIndex instance (:769), and the ordinal, doc-values generation, attributes (always empty) and the last two flags are not taken from it (:766, :773-774, :781-782).

  1. Norms follow. FieldInfo#hasNorms is indexOptions != NONE && omitNorms == false (FieldInfo.java:637-640), and getNormValues returns norms unless omitsNorms() (MemoryIndex.java:2283-2289): a field with no postings gets a norm.
  2. The writer's check is correct. addIndexes(CodecReader...) verifies each source FieldInfo before copying (IndexWriter.java:3288-3294 -> FieldInfos.java:401-409, :511-515 -> FieldInfo.java:276-288). SlowCodecReaderWrapper "Wraps arbitrary readers for merging" (SlowCodecReaderWrapper.java:38), and the MemoryIndex reader reports Version.LATEST metadata (MemoryIndex.java:2292-2294), so the copy reaches the schema check, which is fed the wrong FieldInfo.

What the reader reports (main, assertions off; each document is title + field f)

field fMemoryIndexIndexWriter
NumericDocValuesFieldPOSITIONS, norms, in getIndexedFieldsNONE, no norms
StoredField (int)POSITIONS, norms, in getIndexedFields; FieldExistsQuery counts 1NONE, no norms; FieldExistsQuery throws IllegalStateException
IntPointPOSITIONS, norms, in getIndexedFields; FieldExistsQuery counts 1NONE, no norms; FieldExistsQuery throws
IntField, KnnFloatVectorFieldPOSITIONS, norms, in getIndexedFieldsNONE, no norms
TextField (control)POSITIONS, normsPOSITIONS, norms

terms("f") is null on both sides for every non-indexed kind. The hunt's matrix found the same for all five doc-values types (numeric, sorted and sorted-numeric also with a skip index), IntPoint 1D and 2D, LongField, float and byte vectors and StoredField string, int and binary; the other eight reader kinds it tested showed no exception and no mismatch (only an informational FieldInfo difference on the soft-deletes reader's own soft-deletes field). IntField comes from the devil's-advocate verifier's probe.

The FieldInfo is per field name, not per added instance. A name that also has an instance that indexes terms really has terms and is reported correctly: a TextField and a NumericDocValuesField both named f, in either order, give DOCS_AND_FREQS_AND_POSITIONS with norms in both readers. Only a name whose instances all index nothing gets the wrong metadata.

The two copy failures (main 645d4fe14a20 and 10.6.0; any release from 9.0.0)

  • Into an index holding the same document: IllegalArgumentException: cannot change field "price" from index options=NONE to inconsistent index options=DOCS_AND_FREQS_AND_POSITIONS.
  • Into an empty index, then addDocument of the same document in a new writer: IllegalArgumentException: cannot change field "price" from index options=DOCS_AND_FREQS_AND_POSITIONS to inconsistent index options=NONE.
Exposure and likelihood521 words
  • Who is exposed. Applications that build a MemoryIndex from ordinary Documents with doc-values, point, vector or stored-only fields and then copy its reader into a persistent index with addIndexes(SlowCodecReaderWrapper.wrap(reader)), read its FieldInfos (getIndexOptions, hasNorms, getNormValues, FieldInfos.getIndexedFields), or run FieldExistsQuery on a stored-only or point-only field against it. Default IndexWriterConfig, codec and field classes; the one-field repro is enough, and no dependence on document size is apparent from the code.
  • Effect. On 9.0.0 and later, a clean IllegalArgumentException when the target already holds the field natively. Into an empty target, a committed segment that records the field as indexed with positions and norms; after it, ordinary addDocument calls with that field are rejected, in this and later writer sessions. That lasts until the copied documents are deleted before they are merged with other documents and a new writer is opened, or the index is rebuilt: deleting them in the same writer session does not help, and once they have been merged with other documents the wrong schema stays after deleting them and forceMerge. Not data corruption: the persisted segment is self-consistent and CheckIndex (level 3) is clean after copying this bug's documents (the CheckIndex failures in the hunt's matrix come from term-vector fields, hunt row LFV-788). On 6.0.0 to 8.11.4 both steps are accepted silently and the wrong schema is persisted. FieldExistsQuery on a stored-only or point-only field counts 1 on the MemoryIndex where a real segment throws IllegalStateException (it never matches a document without the field).
  • Scale. One field in one document suffices. In the hunt's matrix of 1,024 one- and two-field documents, copying a default MemoryIndex into an index holding the same document failed on a non-indexed field for 663 of them, in each merge mode (578 with MemoryIndex(true, true), where some fail first on a text field's offsets).
  • Limits to state plainly. The same copy also fails, by MemoryIndex's deliberate design and unchanged by the fix, for StringField and KeywordField (every indexed field gets positions, so DOCS becomes DOCS_AND_FREQS_AND_POSITIONS) and for every document of a MemoryIndex(storeOffsets=true) (text fields get offsets). The executed clean case is the default MemoryIndex(false, false) with positional text fields (the verifier ran only (false, false) and (true, true)); this bug blocks such documents when they also carry doc values, points, vectors or stored-only fields. So the strong claim is the reader's FieldInfo/norms contract; the addIndexes failures are a limited consequence (the devil's-advocate verifier rates the reader defect high confidence and the addIndexes framing medium).
  • Likelihood: rare. Every MemoryIndex with such a field name has the wrong metadata for it, but it shows only through these compositions. No matching public report was identified in the documented searches, although the code has been this way since 6.0.0 (2016).
  • In-tree consumers. lucene/monitor matches a single document through a MemoryIndex(true, true) (DocumentBatch.java:107-113) and a batch of several documents through an IndexWriter (:55-62, :71-74). Its presearcher walks the reader's FieldInfos but skips fields whose terms() is null (TermFilteredPresearcher.java:103-108), so the wrong index options do not change which queries it selects.

Minimal repro

One JUnit test in lucene/memory, public API only: a MemoryIndex from one NumericDocValuesField("price", 42), then the index options of price. It fails on main with assertions on and off, and on the 10.6.0 and 10.5.2 release jars.

TestMemoryIndexDocValuesOnlyFieldInfo.java (lucene/memory test, one method)38 lines
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.index.memory;

import java.util.List;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.index.FieldInfo;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.tests.util.LuceneTestCase;

public class TestMemoryIndexDocValuesOnlyFieldInfo extends LuceneTestCase {

  public void testDocValuesOnlyFieldIsNotIndexed() {
    MemoryIndex mi =
        MemoryIndex.fromDocument(
            List.of(new NumericDocValuesField("price", 42)), new StandardAnalyzer());
    LeafReader reader = mi.createSearcher().getIndexReader().leaves().get(0).reader();
    FieldInfo fi = reader.getFieldInfos().fieldInfo("price");
    // Expected NONE, as IndexWriter records for this field; actual DOCS_AND_FREQS_AND_POSITIONS
    assertEquals("index options of \"price\"", IndexOptions.NONE, fi.getIndexOptions());
  }
}
Command
cp $REPRO/TestMemoryIndexDocValuesOnlyFieldInfo.java $LUCENE/lucene/memory/src/test/org/apache/lucene/index/memory/
cd $LUCENE && ./gradlew :lucene:memory:test --tests org.apache.lucene.index.memory.TestMemoryIndexDocValuesOnlyFieldInfo -Ptests.seed=DEADBEEF -Ptests.asserts=true
# also with -Ptests.asserts=false
Expected
price is reported as IndexOptions.NONE, as IndexWriter records it.
Observed
AssertionError: index options of "price" expected:<NONE> but was:<DOCS_AND_FREQS_AND_POSITIONS>, with assertions on and off.

All files of this repro: repro/LFV-785/

More files, environment, full output214 words

The test sits in the MemoryIndex package by convention; it uses no package-private access. Nothing random reaches the code path: it failed with every seed tried (DEADBEEF through gradle with assertions on and off; DEADBEEF, 5EED1234, 5EED3003 and 5EED3004 on a standalone runner with -ea and -da).

Suggested regression test: this same test, for example as a method of TestMemoryIndex.

The addIndexes consequence is not part of the repro. Adding a TextField title to the document and copying the reader with addIndexes(SlowCodecReaderWrapper.wrap(reader)) into an index that holds the same document throws cannot change field "price" from index options=NONE to inconsistent index options=DOCS_AND_FREQS_AND_POSITIONS, and copying into an empty index makes a later addDocument of the same document throw the reverse message (main 645d4fe14a20 and 10.6.0).

Environment

lucenemain 645d4fe14a20 (11.0.0-SNAPSHOT); also the 10.6.0 and 10.5.2 release jars
jdkOpenJDK 25.0.3 (build 25.0.3+9-LTS)
osLinux aarch64 (the only platform run; the code path is pure Java)

Observed output

TestMemoryIndexDocValuesOnlyFieldInfo > testDocValuesOnlyFieldIsNotIndexed FAILED
    java.lang.AssertionError: index options of "price" expected:<NONE> but was:<DOCS_AND_FREQS_AND_POSITIONS>
        at __randomizedtesting.SeedInfo.seed([DEADBEEF:5261ED1D894D8F3B]:0)
        at junit@4.13.2/org.junit.Assert.fail(Assert.java:89)
        at junit@4.13.2/org.junit.Assert.failNotEquals(Assert.java:835)
        at junit@4.13.2/org.junit.Assert.assertEquals(Assert.java:120)
        at org.apache.lucene.index.memory.TestMemoryIndexDocValuesOnlyFieldInfo.testDocValuesOnlyFieldIsNotIndexed(TestMemoryIndexDocValuesOnlyFieldInfo.java:36)
:lucene:memory:test (FAILURE): 1 test, 1  failure

# the same with -Ptests.asserts=false, on the standalone runner with -ea and -da,
# and on the 10.6.0 and 10.5.2 release jars; with the recommended fix: passes

Why it looks unintended

  • FieldInfo#getIndexOptions promises "IndexOptions.NONE if the field is not indexed"; the field type says NONE, MemoryIndex stores no term, and IndexWriter records NONE.
  • createFieldInfo already takes norms, term vectors, doc values, points and vectors from the field type; the hard-coded options predate MemoryIndex accepting fields without terms (6.0.0).
  • With the fix, no lucene/memory, lucene/monitor or lucene/highlighter test fails.
The full argument350 words
  • Contract. FieldInfo#getIndexOptions: "Returns IndexOptions for the field, or IndexOptions.NONE if the field is not indexed" (FieldInfo.java:564); IndexOptions.NONE: "Not indexed" (IndexOptions.java:29); hasNorms: "Returns true if this field actually has any norms" (FieldInfo.java:637); FieldInfos.getIndexedFields: "fields that have a terms index" (FieldInfos.java:240); LeafReader#getNormValues: "or null if no NumericDocValues were indexed" (LeafReader.java:202-206).
  • The other field-type settings already follow the field type. createFieldInfo copies term vectors, omitNorms, doc values, skip index, points and vectors from the FieldType (MemoryIndex.java:767-768, :771-772, :775-780); payloads follow the MemoryIndex instance flag (:769), and the ordinal, doc-values generation, attributes (always empty) and the soft-deletes and parent flags are not field-type settings (:766, :773-774, :781-782). LUCENE-7679 (#8730) moved the code there to re-use as many FieldType settings as possible and kept only the index options as they were.
  • History. The expression comes from LUCENE-4515 (59476c30cf3, 2012), when every MemoryIndex field had a token stream: up to 5.5.x MemoryIndex rejected doc-values fields and failed on stored-only fields (null token stream); point fields did not exist before 6.0.0. LUCENE-7091 (#8147, doc values) and LUCENE-7093 (#8149, points), both in 6.0.0, started accepting fields without terms and gave them the same options; neither discussion mentions index options. Stored-only fields were accepted from 6.0.0 only as a side effect of their null token stream; MemoryIndex has kept stored values only since PR #11999 (first release 9.5.0), so before 9.5.0 such a field added only the wrong FieldInfo and no value.
  • Precedent. Wrong MemoryIndex FieldInfo bits were fixed as bugs before: LUCENE-6402 (#7462, "MemoryIndex should pass TestUtil.checkReader", the term-vector bit) and LUCENE-7679 (#8730, omitNorms). In PR #907 (LUCENE-10357, closed unmerged) GitHub user jpountz wrote that MemoryIndex should return null Terms only when the field does not exist or its index options are NONE.
  • No upstream test failed with the fix. With fix A2 (and, in the first brief, with fix A) the lucene/memory, lucene/monitor and lucene/highlighter suites pass; this shows only that no tested code depends on the current value.
The devil's-advocate check155 words

Strongest case (the verifiers and the adjudication): MemoryIndex deliberately uses one set of index options per instance. Every indexed field gets positions, keyword fields get fake ones (#12139), and TestMemoryIndex#testKeywordWithoutTokenStream asserts freq and positions for DOCS and DOCS_AND_FREQS field types. MemoryIndex is a single-document search helper that never promised IndexWriter-identical FieldInfos or support as an addIndexes source; FieldInfo APIs are expert; and an indexed field without terms is legal ("ghost fields", LUCENE-10357).

Why it fails: that design covers fields that do index terms, for which MemoryIndex really stores (fake) positions, so their postings match their FieldInfo. For a field whose type says NONE, MemoryIndex stores nothing, and the FieldInfo constructor already normalizes non-indexed fields ("for non-indexed fields, leave defaults", FieldInfo.java:110-114), so reporting NONE does not clash with per-instance payloads. Ghost fields come from merges that drop every value of a field that was indexed; they do not make a never-indexed field indexed.

Suggested fix

Recommended A2: report NONE when the field type indexes nothing, and index terms only when it does

In MemoryIndex: report IndexOptions.NONE when the field type indexes nothing, switch to the usual positions options when a later instance of the name does index terms, serve norms only when hasNorms(), and, like IndexWriter, index terms only for fields whose type is not NONE (one file). The repro and the lucene/memory, lucene/monitor and lucene/highlighter suites pass.

// addField(IndexableField, Analyzer): like IndexWriter, invert only fields whose type indexes something
if (field.fieldType().indexOptions() != IndexOptions.NONE) {
  ... // the existing token-stream / binary-value block; its "else if (... subsumes(DOCS))" becomes "else"
}
// createFieldInfo:
IndexOptions indexOptions =
    fieldType.indexOptions() == IndexOptions.NONE ? IndexOptions.NONE : indexedOptions();
// getInfo: rebuild the FieldInfo with indexedOptions() when a name first added without
// terms gets an instance that indexes terms (FieldInfo's index options are final)
// getNormValues:
if (info == null || info.fieldInfo.hasNorms() == false) {
  return null;
}
All options and trade-offs6 options ยท 1,095 words

The recommended fix is the finder's three-part sketch plus one guard in addField, applied to 645d4fe14a20 in a scratch worktree and run through gradle. The sketch alone, two partial variants and two alternatives are listed with why they fall short.

A2: report NONE when the field type indexes nothing, and index terms only when it does (validated by the checks) (recommended)

Four small changes in MemoryIndex.java (one file, +56/-24, most of it the re-indented addField block): createFieldInfo uses IndexOptions.NONE when fieldType.indexOptions() is NONE, else the old expression (now indexedOptions()); getInfo rebuilds the FieldInfo (its index options are final) with indexedOptions() and that instance's storeTermVectors/omitNorms when a name first added without terms gets an instance that indexes terms (Info.fieldInfo is not final, :1223, so it can be replaced); getNormValues returns null unless fieldInfo.hasNorms(); and addField(IndexableField, Analyzer) runs its existing term-storing block (:624-645) only when the field type's index options are not NONE, as IndexWriter does (IndexingChain.java:1481). The inner else if (... subsumes(IndexOptions.DOCS)) becomes else.

// addField(IndexableField, Analyzer): like IndexWriter, invert only fields whose type indexes something
if (field.fieldType().indexOptions() != IndexOptions.NONE) {
  ... // the existing token-stream / binary-value block; its "else if (... subsumes(DOCS))" becomes "else"
}
// createFieldInfo:
IndexOptions indexOptions =
    fieldType.indexOptions() == IndexOptions.NONE ? IndexOptions.NONE : indexedOptions();
// getInfo: rebuild the FieldInfo with indexedOptions() when a name first added without
// terms gets an instance that indexes terms (FieldInfo's index options are final)
// getNormValues:
if (info == null || info.fieldInfo.hasNorms() == false) {
  return null;
}

Trade-offs

  • All parts are needed: createFieldInfo alone would report a name first added as a doc value and then as a TextField as NONE although it has terms, because getInfo builds the FieldInfo once; norms must follow hasNorms(); and without the addField guard a custom field whose type says NONE but whose token stream emits terms would be reported NONE while its terms stay searchable (option A).
  • Keeps MemoryIndex's deliberate design for fields that index terms (positions everywhere, offsets with storeOffsets), so StringField/KeywordField and storeOffsets copies still fail as before.
  • Behaviour change: FieldExistsQuery on a stored-only or point-only MemoryIndex field throws IllegalStateException, as on a real segment, instead of matching (executed with A2 for StoredField and IntPoint with assertions on, and with fix A).
  • Behaviour change: a custom field whose type says NONE but whose token stream emits terms is no longer searchable in a MemoryIndex. It never was in an IndexWriter index, and lucene/monitor matches a single document with a MemoryIndex and several with an IndexWriter (DocumentBatch.java:55-62, :71-74, :107-113), so the two batch kinds then agree. Stock field classes never produce such a field (Field.java:466-468). A2 does more than drop the terms: its guard sits before the token-stream call, so addField no longer calls tokenStream() for any field whose type says NONE, as IndexWriter (IndexingChain.java:1481). A custom IndexableField whose tokenStream() has side effects (validation that throws, resources it opens or closes) no longer sees them run in a MemoryIndex; whether anyone relies on that is unknown.
  • Test gap: the custom-field case is shown only by standalone probes; neither the suggested regression test nor any gradle suite exercises it. Proposing A2 rather than A likely needs a focused test of a custom NONE field with a token stream, and agreement that such a field should not be searchable.
  • The getInfo part also takes omitNorms from the first instance that indexes terms, which overlaps the first-instance case left by #8730 (LUCENE-7679).

What the checks validated

  • Gradle, fresh scratch worktree of 645d4fe14a20, seed DEADBEEF: stock + test fails; with A2 the whole :lucene:memory:test passes, 4 suites, 52 tests (the repro, TestMemoryIndex 30, TestMemoryIndexAgainstDirectory 16, TestSlicedIntBlockPool 5), 0 failures, with assertions on and off; lucene/monitor (32 suites, 181 tests, 1 skipped) and lucene/highlighter (34 suites, 722 tests) pass with assertions on; :lucene:memory:check -x test passes (google-java-format, ecjLint, forbiddenApis).
  • Standalone runner with the A2 class shadowed: the repro passes; the finder's four tests pass (both addIndexes directions succeed); a field-name probe matches IndexWriter in all four cases, including the custom NONE field with a token stream (NONE, no norms, no terms, 0 matches).
  • Not run: the full Lucene test suite, nightly tests, benchmarks.

A: the same without the addField guard (the finder's sketch) (validated by the checks)

The first three parts of A2 only (one file, +33/-4): createFieldInfo takes NONE from the field type, getInfo upgrades on the first instance that indexes terms, getNormValues follows hasNorms().

Trade-offs

  • Smaller and enough for every stock field class.
  • Gap: addField stores the terms of any non-null token stream whatever fieldType.indexOptions() says (MemoryIndex.java:636-637), while IndexWriter inverts a field only when its index options are not NONE (IndexingChain.java:1481). For a custom IndexableField whose type says NONE and whose tokenStream returns tokens (executed): IndexWriter reports NONE and no terms; stock MemoryIndex reports positions, norms and searchable terms; with fix A MemoryIndex reports NONE without norms but still serves the terms (terms("f") non-null, a TermQuery matches), so its reader disagrees with itself. A2 closes this.

What the checks validated

  • In the first brief (gradle, scratch worktree of 645d4fe14a20): the whole lucene/memory suite, 52 tests, assertions on and off; lucene/monitor 181 tests (1 skipped) and lucene/highlighter 722 tests, 0 failures; :lucene:memory:check -x test passes; the finder's four tests pass on the standalone runner.
  • In the hunt (standalone runner, a9779ef3c193): the finder's 4 tests, the independent reproduction's 8 tests, the cross-model reviewers' own tests and the intended-behaviour check's probe pass with it.

Only change createFieldInfo (not enough)

Use NONE when the field type indexes nothing, nothing else.

Trade-offs

Mis-reports a name whose first instance has no terms and a later one has (read from getInfo, not run); leaves the norms keyed on omitsNorms().

Only change getNormValues (not enough)

Return null unless hasNorms().

Trade-offs

Removes nothing as long as the index options stay hard-coded (hasNorms() stays true); the addIndexes failures remain (read, not run).

Use the field type's index options for every field (not recommended)

Full IndexWriter parity, also for fields that index terms.

Trade-offs

Would also fix the StringField/KeywordField copies, but contradicts MemoryIndex's deliberate positions-for-every-indexed-field design that TestMemoryIndex#testKeywordWithoutTokenStream asserts and #12139 built on (read, not run).

Document that MemoryIndex FieldInfos differ from IndexWriter's (not recommended alone)

Say in the javadoc that the reader is not an addIndexes source and its metadata is approximate.

Trade-offs

Leaves the FieldInfo#getIndexOptions contract broken for every consumer of the reader.

Downstream note

A product that copies MemoryIndex readers into its own indexes can, after one copy into an empty index (9.0.0 and later), be unable to add documents with that field, also in later writer sessions; the trigger is the application's own schema, not field values.

How it was found

A bounded-exhaustive differential check copied every one- and two-field document built from 32 field kinds, from 10 kinds of reader, into indexes with addIndexes and compared the result with ordinary indexing. Only the two MemoryIndex readers failed, on every field kind that indexes no terms; the case was then reduced to a one-method JUnit test.

The method in full190 words

Method: semi-formal (bounded-exhaustive translation validation of the real jar), a hunt unit, 2026-10-27. The harness copied 32 field kinds as single-field documents and all 992 ordered pairs (1,024 documents) from 10 reader kinds (SegmentReader, one with a deleted document, MemoryIndex default and with offsets and payloads, ParallelLeafReader, FilterLeafReader, an ExitableDirectoryReader leaf, a SoftDeletesDirectoryReaderWrapper leaf, SortingCodecReader, FilterCodecReader), with a plain and a reordering merge policy, into three targets (empty then a native addDocument and forceMerge, holding the same document, holding it with the fields reversed). Oracles: no exception, equal content dumps, CheckIndex with slow checks. 20,480 cases and 123,410 checks with -ea on a9779ef3c193; replicated with 36 field kinds and a deliberately broken mutant reader with -da (28,512 cases, 174,514 checks; the mutant was caught).

Exceptions came only from the two MemoryIndex kinds. Besides this bug, the matrix also surfaced two related, already known MemoryIndex FieldInfo problems at the same code (term vectors without the FieldInfo flag, #7462 family, and settings taken from the first instance of a name, #8730), tracked as separate hunt rows (LFV-788 and LFV-342). The unit started from an executed lead of an earlier unit.

Prior reports, history and checks

Prior reports

No prior report found (GitHub issues, PRs and commits of Lucene and three downstream projects, open PRs, mailing lists, the JIRA archive, the web; refreshed 2026-10-30). Closest: #8730 (LUCENE-7679), about omitNorms only.

Searches7 searches

Context, not reports: #8730 / LUCENE-7679 wrote today's createFieldInfo and kept the hard-coded options (a strict reading of its title could make this "known"; the adjudication and the prior-art search judged it new and the registry keeps "new"; the second cross-model fact check would count it as known for that reason); #8147 and #8149 added doc-values and point fields without discussing index options; PR #11999 (stored fields; GitHub user uschindler noted that merging a MemoryIndex into a conventional index goes through the LeafReader interface); PR #13633 (vectors); PR #907 (ghost fields, closed unmerged; jpountz's remark that MemoryIndex should return null Terms only if the field does not exist or its index options are NONE); #8447 and #8735 (open: immutability, per-field payloads); #7462 / LUCENE-6402 (the term-vector bit). Search limits: keyword search only; closed unmerged PRs that change MemoryIndex.java without naming it were not listed by file; one Stack Overflow question was seen by title only.

What was searched, when, and what turned up
WhereWhenHowFound
GitHub (apache/lucene issues, PRs, commits; Elasticsearch, OpenSearch, Solr)2026-10-27two scripted prior-report searches (96 queries: 32 issue/PR, 3 commit, 4 downstream, 40 mailing-list, 8 JIRA-archive, 9 web) and 11 manual searches; the finder's 45 executed queries (6 web queries pending) and the devil's advocate's 40 earlier queriesno report; 29 logged hits, all about something else
Open apache/lucene PRs and file history2026-10-27changed files of all 409 open PRs; git log of MemoryIndex.java (-S DOCS_AND_FREQS_AND_POSITIONS, -S IndexOptions.NONE)17 open PRs touch the memory module or FieldInfo files; none changes the index options in createFieldInfo
Mailing lists, JIRA archive, web2026-10-27dev@, java-user@, issues@, commits@; LUCENE-7091, LUCENE-7093, LUCENE-7679, LUCENE-6402, LUCENE-10357 readno report; a 2022 java-user@ thread documents addIndexes(SlowCodecReaderWrapper.wrap(...)) as a copy pattern
Refresh2026-10-30upstream head and MemoryIndex.java history; open PRs mentioning MemoryIndex; issue and PR searches for MemoryIndex with IndexOptions, addIndexes, FieldInfo, norms, SlowCodecReaderWrapper, inconsistent, FieldExistsQuery, createFieldInfono new item; no open PR mentions MemoryIndex; the file last changed with #16383 (2026-07-27)
Refresh2026-10-30upstream main head and MemoryIndex.java history; open PRs mentioning MemoryIndex; issues and PRs mentioning MemoryIndex updated since 2026-10-27no new item; MemoryIndex.java last changed with #16383 (2026-07-27)
Refresh2026-10-30upstream main head and MemoryIndex.java history; open PRs mentioning MemoryIndex (the issue searches hit the API rate limit)no new item; upstream main 2f424fd428da; MemoryIndex.java unchanged since #16383
Refresh (second-round fact check)2026-10-30issues and PRs mentioning MemoryIndex updated since 2026-10-27; MemoryIndex with addIndexes and createFieldInfo; upstream main headno issue and no PR mentioning MemoryIndex updated since 2026-10-27; upstream main still 2f424fd428da

History

Since 6.0.0 (LUCENE-7091, cf3eea264066, 2016, then points in LUCENE-7093); exceptions since 9.0.0, when the writer's strict schema checks (LUCENE-9334) arrived; unfixed on main and in 10.6.0.

Versions tested7 rows

LUCENE-7091 "Added doc values support to memory index" (GitHub issue #8147, a JIRA patch, no PR; Martijn van Groningen) is cf3eea264066 on main (committed 2016-03-15), 559432fcdcd8 on branch_6x and bd0803fd41c6 on branch_6_0, the one in 6.0.0. LUCENE-7093 (#8149, points, fe58313de027 on branch_6_0) widened it in the same release. The hard-coded expression itself is from LUCENE-4515 (59476c30cf3, 2012), correct while every MemoryIndex field had terms: up to 5.5.x MemoryIndex rejected doc-values fields and failed on stored-only fields, and point fields did not exist before 6.0.0. Stored values have been kept only since PR #11999 (first release 9.5.0; 89cfaa37720 on branch_9x, 66127f6e696 on main); before that a stored-only field added only the wrong FieldInfo. LUCENE-7679 (cecc44c731b in 6.5.0, 6696eafaae18 on main) moved it into createFieldInfo unchanged. LUCENE-9334 (d03662c48bfc, PR #11, 2021-04-14, 9.0.0) added the strict per-field schema checks that now reject the copy; they are correct. All 83 release tags from 6.0.0 to 10.5.2 contain the expression (read); the version table is from executed probes.

Versions and branches executed, with results
Version or commitVerdictResult
5.5.0, 5.5.5 API absentMemoryIndex rejects doc-values fields and fails on stored-only fields; point fields do not exist before 6.0.0
6.0.0, 6.0.1, 6.1.0, 6.4.2, 6.5.0, 6.6.6, 7.7.3, 8.11.4 silentwrong FieldInfo (positions, norms); the copy and the later addDocument are accepted and the wrong schema is persisted
9.0.0, 9.1.0, 9.12.3, 10.5.2 affectedwrong FieldInfo, and both addIndexes scenarios throw
10.6.0 (4ae946e5a9b) affectedthe repro fails; the finder's test fails 3 of 4 (both addIndexes messages)
main 645d4fe14a20 affectedthe repro fails (gradle; standalone runner with assertions on and off)
upstream main 2f424fd428da readsame code: MemoryIndex.java last changed by #16383 (2026-07-27), read 2026-10-30
with the fix (main 645d4fe14a20) not affectedwith fix A2 (recommended) or fix A: the repro passes, and so do the lucene/memory, lucene/monitor and lucene/highlighter suites

Independent checks

The finder, cross-model reviews (OpenAI GPT-5.6, with code execution), more verifiers, an adjudication and a credibility audit rate it real; this doc's runs reproduced it on main, 10.6.0 and 10.5.2 and validated the fix.

Checks and verdicts441 words

Every check ran a repro of its own or re-ran one on stock jars. The hunt and its reviews ran on main a9779ef3c193 (the cross-model reviews' records name ea57360912ed); MemoryIndex, FieldInfo, FieldInfos and IndexWriter are identical there, and MemoryIndex.java is identical at 645d4fe14a20.

Each independent check and its verdict
CheckVerdictWhat it found
finder (a hunt unit) candidateBounded-exhaustive copy matrix (20,480 cases, replicated with 28,512 and a caught mutant): exceptions only for the MemoryIndex kinds. Public-API JUnit fails 3 of 4 on main, 10.5.2, 9.12.3, 9.1.0; passes with the fix.
cross-model review, refute lens (OpenAI GPT-5.6) real, highOwn doc-values-only test fails 9 of 12 (-ea) and 6 of 8 (-da); indexed-text control passes; 12 of 12 with the fix; intended-behaviour argument weak.
Independent reproducer realOwn 8-test JUnit fails 6 of 8 on main, 10.5.2 and 9.12.3 (doc-values, stored and point fields; both copy directions); controls pass; 8 of 8 with the fix.
Cross-model review, intended lens (OpenAI GPT-5.6) real, highOwn binary-doc-values tests: the same on main, 10.5.2 and 9.12.3; wrong metadata without an exception on 8.11.4; upstream memory tests pass with the fix.
Devil's advocate real; addIndexes framing limitedFound the FieldExistsQuery symptom and the StringField/KeywordField/storeOffsets limits; with the fix all five non-indexed kinds match IndexWriter.
adjudication confirmed, highRe-ran the independent reproduction's test with a new seed (6 of 8 fail, -ea and -da; 8 of 8 with the fix) and the finder's (3 of 4 fail, -da).
credibility audit keepRe-ran the finder's test with a fresh seed on main: 3 of 4 fail; class and score kept; reach caveats.
fact checks, two rounds (Claude; OpenAI GPT-5.6), and the diagram checks errors and overclaims found, all fixed but one novelty callRound 1: the persistence clause, the 5.5.x wording, the per-name scope, version qualifiers and the custom-field edge case, which fix A2 now closes. Round 2: the registry status, the matrix citation, the "not affected" scope, the clean-copy case, a storeDocValues citation, A2's skipped tokenStream() call and its test gap, and the diagrams now show A2. The reviewer's view that #8730 makes the bug "known" is left to the registry, which keeps "new".
this doc's runs reproduced, fix validatedThe one-method test on main through gradle and on the standalone runner (assertions on and off), and on 10.6.0 and 10.5.2; the finder's test on main and 10.6.0; fix A2 (and, in the first brief, fix A) with the memory suite and its in-tree consumers; the field-name probe with the fix.

Diagram file

The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.

Sources and provenance

Finding record, Lucene and upstream sources, repro files and diagram notes1,611 words

Finding record

LFV-785, snapshot of 2026-11-06

Registry title
MemoryIndex reports a field that indexes no terms (doc values, points, vectors or stored only) as indexed with positions and norms, so IndexWriter.addIndexes(SlowCodecReaderWrapper.wrap(reader)) fails next to the same document, or records the wrong schema in an empty index, after which the writer rejects that document
Area
memory / MemoryIndex reader FieldInfos (index options and norms of fields that index no terms); seen through the IndexWriter.addIndexes(CodecReader...) schema check and FieldExistsQuery
Priority
Low, rank 602: MemoryIndex reporting term-less fields as indexed mostly fails loudly at addIndexes and needs a rare composition to persist a wrong schema.
Impact class
crash
Impact note
Applications that put an ordinary Document with a field that indexes no terms (NumericDocValuesField and the other doc-values fields, IntPoint, the point and doc-values parts of IntField or LongField, KnnFloatVectorField, StoredField) into a MemoryIndex and copy its reader into a persistent index with IndexWriter.addIndexes(SlowCodecReaderWrapper.wrap(reader)) get IllegalArgumentException 'cannot change field ... inconsistent index options' when the target already holds that field as indexed natively, or, into an empty index, a committed segment that records the field as indexed with positions and norms, after which every ordinary addDocument with that field is rejected, also in later writer sessions, until the index is rebuilt; default IndexWriterConfig, codec and field classes, any scale, exceptions on 9.0.0 to 10.6.0 and main (6.0.0 to 8.11.4 accept the copy and silently persist the wrong schema); the wrong reader metadata is present for every such field in every MemoryIndex, but term and phrase queries are unaffected, and the only other in-tree symptom found is FieldExistsQuery matching a stored-only or point-only field where a real segment throws its documented IllegalStateException.
Summary
You build a MemoryIndex from a Document with a TextField title and a NumericDocValuesField price=42 (or a StoredField, IntPoint or vector field) and read its FieldInfo for price, or copy the reader into an index with IndexWriter.addIndexes(SlowCodecReaderWrapper.wrap(reader)), and expect what IndexWriter records for the same document: price is not indexed (IndexOptions.NONE, no norms), so the copy fits next to natively indexed copies of the document. Instead MemoryIndex reports price as DOCS_AND_FREQS_AND_POSITIONS with norms although it holds no terms for it, so addIndexes into an index holding the same document throws 'cannot change field "price" from index options=NONE to inconsistent index options=DOCS_AND_FREQS_AND_POSITIONS'. A copy into an empty index succeeds but records price as indexed, and addDocument of the same document then throws the reverse message, also in a new writer session.
Introduced
cf3eea264066 on main (LUCENE-7091 'Added doc values support to memory index', a JIRA patch, now GitHub issue #8147, no PR; backports 559432fcdcd8 on branch_6x and bd0803fd41c6 on branch_6_0, the one in 6.0.0), widened in the same release by LUCENE-7093 (#8149, point values, fe58313de027 on branch_6_0) [read: history clone; exec: 5.5.0 and 5.5.5 reject these field types, 6.0.0 has the bug]. Before it every MemoryIndex field had a token stream, so the hard-coded 'storeOffsets ? DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS : DOCS_AND_FREQS_AND_POSITIONS' (from LUCENE-4515, 59476c30cf3, 2012) was accurate; LUCENE-7091 accepted fields without a token stream and gave them the same options. LUCENE-7679 (6696eafaae18; cecc44c731b in 6.5.0) moved the expression into createFieldInfo unchanged, LUCENE-9570 (7f8b7ffbcad2) only reformatted it. The visible exceptions come from the correct strict schema checks of LUCENE-9334 (d03662c48bfc, PR #11, 9.0.0), which are fed the wrong FieldInfo (2016-03-15 (committed; authored 2016-03-09); the addIndexes exceptions appear with LUCENE-9334, 2021-04-14)
First affected version
6.0.0 [exec] (wrong reader-side FieldInfo; the finder and judge said 6.1, corrected by the version-history runs: LUCENE-7091 and LUCENE-7093 are in 6.0.0); the addIndexes exceptions first in 9.0.0 [exec]
Affected versions
Reader-side defect in every release from 6.0.0 to 10.6.0 (10.6.0 [exec] on the release jars, in the doc) and on main a9779ef3c193. [exec] the version-portable history probe (doc-values-only and stored-only index options, norms, both addIndexes scenarios) on 6.0.0, 6.0.1, 6.1.0, 6.6.6, 7.7.3, 8.11.4, 9.0.0, 9.12.3, 10.5.2 and main, and the finder's probe on 6.4.2 and 6.5.0; the finder's JUnit fails on 9.1.0, 9.12.3, 10.5.2 and main (-ea and -da on main); the independent reproduction's JUnit on main (-ea and -da), 10.5.2 and 9.12.3. addIndexes exceptions on 9.0.0 to 10.6.0 and main [exec]; 6.0.0 to 8.11.4 accept the copy and the later addDocument without an error and persist the wrong FieldInfo [exec]. Not affected: 5.5.0 and 5.5.5, where MemoryIndex rejects doc-values, point and stored-only fields [exec]. [read] the releases between the executed points: all 83 release tags from 6.0.0 to 10.5.2 contain the hard-coded expression and none reads fieldType.indexOptions() when building the FieldInfo. Unfixed: upstream main f7bcc0abd81 (2026-10-27) has no change under lucene/memory, and no open apache/lucene PR changes createFieldInfo's index options. Not checked: unreleased branches past 10.6.0, x86
Status
documented (brief, checks and a per-finding doc exist)
Verification
Confirmed by AI-run checks: an independent repro, a cross-model review that agreed, an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
Found by
semi-formal: exhaustive check
Upstream and prior reports
none found (searched 2026-10-27, read-only; the prior-report record, verdict new, confidence medium-high): two scripted prior-report searches (96 queries: 32 GitHub issue/PR, 3 commit, 4 downstream, 40 mailing-list over dev@, java-user@, issues@ and commits@, 8 JIRA archive, 9 web), 11 manual GitHub searches (apache/lucene, Elasticsearch, OpenSearch, Solr), the changed files of all 409 open apache/lucene PRs, the git history of MemoryIndex.java, and the finder's (51 queries) and the intended-behaviour check's (40) earlier runs. Related, not reports of this bug: #8730 / LUCENE-7679 (closed, fixed in 6.5.0; generic title 'MemoryIndex.addField() ignores some FieldType settings', but body, comments, fix and test are only about omitNorms of indexed fields and scores; it wrote createFieldInfo and kept the hard-coded index options; the known overlap of LFV-342), #8147 / LUCENE-7091 and #8149 / LUCENE-7093 (added doc-values and point fields to MemoryIndex; index options never discussed), PR #907 / #11393 (LUCENE-10357 ghost fields, closed unmerged; GitHub jpountz wrote there that MemoryIndex should return null Terms only if the field does not exist or does not index terms, indexOptions == NONE), PR #11999 (stored fields; GitHub uschindler: merging a MemoryIndex into a conventional index goes through the LeafReader interface), PR #13633 (vectors), #8735 / LUCENE-7684 and #8447 / LUCENE-7394 (open; per-field payloads and offsets), #7462 / LUCENE-6402 (FieldInfo term-vector bit, 2015). Not fixed: upstream main f7bcc0abd81 (2026-10-27) unchanged under lucene/memory; no open PR changes createFieldInfo's index options. Gaps: Stack Overflow question 78129318 read by title only; closed unmerged PRs that touch MemoryIndex.java without naming it were not enumerated
Lucene commit tested
main 645d4fe14a20 (11.0.0-SNAPSHOT, commit of 2026-10-28) in scratch worktrees through gradle: the repro test stock (assertions on and off); with fix A2 (recommended) the whole lucene/memory suite (assertions on and off), the lucene/monitor and lucene/highlighter suites and :lucene:memory:check -x test; the same with fix A in the first brief. On the standalone runner: the repro (-ea, -da), the finder's four-test class and a field-name probe, stock and with the fix. Release jars 10.6.0 (4ae946e5a9b) and 10.5.2. The hunt and its reviews ran on main a9779ef3c193, where MemoryIndex.java is the same, and the history probe on release jars from 5.5.0 to 10.5.2. Upstream main 2f424fd428da was read on 2026-10-30. Only Linux aarch64 and OpenJDK 25.0.3 were run.

Lucene and upstream sources

Finding record
  • lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:759-783 (at a9779ef3c193) (createFieldInfo: indexOptions = storeOffsets ? DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS : DOCS_AND_FREQS_AND_POSITIONS, while term vectors, omitNorms, doc values, skip index, points and vectors come from the FieldType), :732-757 (getInfo: the FieldInfo is built from the first instance of a name), :2283-2289 (getNormValues: norms unless fieldInfo.omitsNorms())
  • lucene/core/src/java/org/apache/lucene/index/FieldInfo.java:564 (at a9779ef3c193) ('Returns IndexOptions for the field, or IndexOptions.NONE if the field is not indexed'), :637-640 (hasNorms 'Returns true if this field actually has any norms' = indexOptions != NONE && !omitNorms), :279 (verifySameIndexOptions); IndexOptions.java ('NONE: Not indexed'); FieldInfos.java:240 (getIndexedFields), :406, :515 (verifyFieldInfo / verifySameSchema)
  • lucene/core/src/java/org/apache/lucene/index/IndexWriter.java:3289-3294 (at a9779ef3c193) (addIndexes(CodecReader...) checks every source FieldInfo against the writer's schema); SlowCodecReaderWrapper.java:37-41 ('Wraps arbitrary readers for merging'); search/FieldExistsQuery.java:291 ('FieldExistsQuery requires that the field indexes doc values, norms or vectors')
  • a Lucene main checkout: git diff ea57360912ed a9779ef3c193 touches none of lucene/memory, FieldInfo.java, FieldInfos.java or IndexWriter.java (SlowCodecReaderWrapper.java gains only a vector-access overload)
The finding in 30 seconds
  • a Lucene main checkout (645d4fe14a20) lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:620-680 (addField(IndexableField, Analyzer)), :732-757 (getInfo), :759-783 (createFieldInfo), :2283-2289 (getNormValues), :2292-2294 (getMetaData)
  • a Lucene main checkout (645d4fe14a20) lucene/core/src/java/org/apache/lucene/index/FieldInfo.java:110-114, :276-288, :564, :637-640
  • a Lucene main checkout (645d4fe14a20) lucene/core/src/java/org/apache/lucene/index/FieldInfos.java:401-409, :511-515; index/IndexWriter.java:3275, :3288-3294; index/SlowCodecReaderWrapper.java:38
  • a Lucene main checkout (645d4fe14a20) lucene/core/src/java/org/apache/lucene/document/Field.java:466-468 (tokenStream is null for NONE)
Who is affected
  • a Lucene main checkout (645d4fe14a20) lucene/core/src/java/org/apache/lucene/search/FieldExistsQuery.java:38-41 (class javadoc; it does not mention the exception), :156, :211, :258, :290-291
  • a Lucene main checkout (645d4fe14a20) lucene/monitor/src/java/org/apache/lucene/monitor/DocumentBatch.java:55-62, :71-74, :107-113; lucene/monitor/src/java/org/apache/lucene/monitor/TermFilteredPresearcher.java:103-108
Why it looks unintended
  • a Lucene main checkout (645d4fe14a20) lucene/core/src/java/org/apache/lucene/index/FieldInfo.java:110-114, :564, :637-640; index/IndexOptions.java:29; index/FieldInfos.java:240; index/LeafReader.java:202-206
  • a Lucene main checkout (645d4fe14a20) lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:759-783
  • a Lucene main checkout (645d4fe14a20) lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java:1093-1176 (testKeywordWithoutTokenStream)
  • a Lucene main checkout (645d4fe14a20) lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:421, :475, :743, :766-782; lucene/core/src/java/org/apache/lucene/index/IndexableFieldType.java:121
Suggested fix
  • a Lucene main checkout (645d4fe14a20) lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:620-680 (addField; term block :624-645, :636-637), :732-757 (getInfo builds the FieldInfo once), :1223 (Info.fieldInfo is not final), :2283-2289 (getNormValues)
  • a Lucene main checkout (645d4fe14a20) lucene/core/src/java/org/apache/lucene/index/IndexingChain.java:1481; lucene/core/src/java/org/apache/lucene/document/Field.java:466-468; lucene/core/src/java/org/apache/lucene/index/FieldInfo.java:637-640
  • a Lucene main checkout (645d4fe14a20) lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndex.java:1093-1176 (testKeywordWithoutTokenStream)
  • a Lucene main checkout (645d4fe14a20) lucene/monitor/src/java/org/apache/lucene/monitor/DocumentBatch.java:55-62, :71-74, :107-113
  • a Lucene main checkout (645d4fe14a20) lucene/core/src/java/org/apache/lucene/index/IndexingChain.java:1477-1489, :1950, :1981 (IndexWriter calls tokenStream only inside invert, which runs only for index options other than NONE)

Other statements trace to the hunt's working files (models, logs, reviews and briefs), which are not published.

Repro files

About the diagrams

Made in parallel with this doc from the code at 645d4fe14a20 and the executed repro runs.

Built 2026-11-06 from the hunt's record of this finding and its doc file. Lucene main under test in this hunt: ea57360912ed. Every listed finding was re-run on Lucene main e1faeba08bde on 2026-11-02.