All findings · Lucene finding · LFV-333 · memoryindex-ignores-custom-term-frequency

MemoryIndex ignores TermFrequencyAttribute, so FeatureField values and custom term frequencies are indexed as frequency 1

MemoryIndex ignores TermFrequencyAttribute, so FeatureField values and custom term frequencies become 1: a FeatureField worth 10 scores 4.5918E-41 instead of 10.0 (linear), or exactly 0.0, "no match", instead of 0.5 (saturation with the pivot 10), although the document matches. Lucene Monitor inherits it for single documents (since 8.6.0).

Summary

Priority
Medium, rank 239Since 7.0, MemoryIndex ignores custom term frequencies, so FeatureField scores collapse while matching is unchanged for the tested queries, a silent scoring error like the norms row LFV-105, which it follows.
Impact
silent wrong results
Since
7.0.0 (LUCENE-7854, 2017); FeatureField since 7.4.0introduced 2017-06-06 · about 9 years
Affected
7.0.0 to 10.6.0 (10.6.0 by reading) and main
Status
documented
Found by
translation validation of MemoryIndex against IndexWriter on the real jar

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

FeatureField and any TokenStream that sets TermFrequencyAttribute tell Lucene how often a term occurs instead of repeating it. IndexWriter keeps that number. MemoryIndex, the one-document in-memory index, ignores it and counts each token once.

Summary card: MemoryIndex ignores TermFrequencyAttribute, custom term frequencies become 1. Example: one doc with FeatureField("features", "pagerank", 10f), indexed with IndexWriter and with MemoryIndex.fromDocument. Step 1: FeatureField stores 10f as the term frequency Float.floatToIntBits(10f) >>> 15 = 33344. Step 2: IndexWriter stores freq 33344; MemoryIndex.storeTerms never reads the attribute and stores freq 1 (numTokens++, freq[ord]++, sumTotalTermFreq++). Step 3: queries decode the frequency, 33344 to 10.0 but 1 to 4.5918E-41; the hit count is unchanged, scores and term statistics are wrong. Table, expected (IndexWriter) vs actual (MemoryIndex): postings freq 33344 vs 1; newLinearQuery score 10.0 vs 4.5918E-41; newSaturationQuery(..., 1f, 10f) score 0.5 vs 0.0 (search(): no match); Lucene Monitor, two-document batch vs document alone, linear / sat: 10.0 / 0.5 vs 4.5918E-41 / no match; DOCS_AND_FREQS a:7 b:1, freq(a), sumTotalTermFreq, norm, BM25(a): 7, 8, 8, 0.2456 vs 1, 2, 2, 0.1308. Same on both paths: the hit count (count = 1); only score-based matching drops a 0.0 score. Affected: every release checked from 7.0.0 (FeatureField from 7.4.0) to 10.5.2 and main; also DelimitedTermFrequencyTokenFilter fields and DOCS_AND_CUSTOM_FREQS fields (from 10.5.0).
One FeatureField value, 10f, stored as the frequency 33344: IndexWriter keeps it and scores 10.0 and 0.5, MemoryIndex counts the token once, so the frequency 1 decodes to 4.5918E-41 and the saturation score is 0.0, and Monitor scores the document differently alone than in a batch. Select the still to open it at full size. Generated with AI, checked against the code.
  1. You put one document with FeatureField("features", "pagerank", 10f) into a one-document IndexWriter index and into MemoryIndex.fromDocument. FeatureField stores the value as a term frequency: 10f becomes 33344.
  2. You search FeatureField.newLinearQuery("features", "pagerank", 1f) and expect the score 10.0 from both, as for ordinary fields. IndexWriter gives 10.0.
  3. MemoryIndex never reads the frequency attribute and counts the token once. Frequency 1 decodes to 4.5918E-41, so the score is 4.5918E-41. With newSaturationQuery (pivot 10) it is exactly 0.0 instead of 0.5, which MemoryIndex.search documents as "no match", while count() says the document matches.
  4. Custom frequencies on text fields shrink the same way: a DOCS_AND_FREQS term a with frequency 7 gets freq 1, sumTotalTermFreq 2 instead of 8, norm 2 instead of 8, and BM25 0.1308 instead of 0.2456.
  5. Lucene Monitor (since 8.6.0) matches one document on a MemoryIndex and two or more on an IndexWriter reader: the same document scores 10.0 in a batch of two and 4.5918E-41 alone, and a saturation query with the pivot 10 is not reported for it at all.

Who is affected

Code that scores FeatureField queries or reads term statistics of custom-frequency fields on a MemoryIndex: Lucene Monitor with ScoringMatch matching single documents (since 8.6.0), callers of MemoryIndex.search (whose documented match test is score > 0), and DelimitedTermFrequencyTokenFilter or DOCS_AND_CUSTOM_FREQS fields. Default settings, opt-in custom frequencies, every time; for the tested queries the matching documents stay the same.

Mechanism, terms and numbers759 words

FeatureField stores its value as a term frequency, and any TokenStream can set a custom frequency through TermFrequencyAttribute (for example DelimitedTermFrequencyTokenFilter on a|7, or a DOCS_AND_CUSTOM_FREQS field since 10.5.0). For DOCS_AND_FREQS and DOCS_AND_CUSTOM_FREQS fields IndexWriter indexes that frequency; MemoryIndex.storeTerms never reads the attribute and adds 1 per token to the postings frequency, sumTotalTermFreq and the field length. A document put into a MemoryIndex (fromDocument, addField(IndexableField, Analyzer), or Lucene Monitor matching one document) therefore counts each such token as frequency 1: FeatureField("features", "pagerank", 10f) is frequency 33344 in an IndexWriter index and 1 in a MemoryIndex, which decodes to 4.5918E-41. FeatureField.newLinearQuery scores 4.5918E-41 instead of 10.0, and newSaturationQuery with the pivot 10 scores exactly 0.0 instead of 0.5 although the document matches; 0.0 is what MemoryIndex.search documents as "no match". Since 8.6.0, Monitor scores the same document 10.0 in a two-document batch and 4.5918E-41 alone, and drops a stored saturation query with the pivot 10 for the single document. For the tested queries the set of matching documents (IndexSearcher.count) is unchanged; only score-based match tests lose the match. Every release checked from 7.0.0 to 10.6.0 (10.6.0 by reading) is affected; the validated fix reads the attribute where IndexWriter honours it and rejects overflow as IndexWriter does.

Terms

  • TermFrequencyAttribute: a token attribute that "Sets the custom term frequency of a term within one document"; the field must be indexed with DOCS_AND_FREQS (TermFrequencyAttribute.java:24-26 on main a9779ef3c193). DelimitedTermFrequencyTokenFilter sets it from text like a|7; since 10.5.0 a DOCS_AND_CUSTOM_FREQS field treats it as a per-term score, adding 1 per term to the field length.
  • FeatureField: a rank-feature field (pagerank, learned-sparse term weights). "Feature values are internally encoded as term frequencies" (FeatureField.java:47): the value becomes Float.floatToIntBits(value) >>> 15 (:185-186; 10f gives 33344), set through the attribute (:222) on a DOCS_AND_FREQS field (:114). Its queries decode the frequency back with Float.intBitsToFloat(freq << 15) (:240-249).
  • MemoryIndex: a single-document index held in memory, "a replacement/substitute for RAM-resident Directory implementations" (MemoryIndex.java:106). Lucene Monitor uses one for a one-document batch and an IndexWriter-built reader for larger batches (DocumentBatch.java:44-62, 105-113).

The bug

  1. IndexWriter uses the attribute, for DOCS_AND_FREQS and DOCS_AND_CUSTOM_FREQS fields. FreqProxTermsWriterPerField.getTermFreq (FreqProxTermsWriterPerField.java:208-219) takes it as the postings frequency, and IndexingChain adds it to the field length (1 per term for DOCS_AND_CUSTOM_FREQS) with Math.addExact (IndexingChain.java:2053-2064). A DOCS field keeps no frequencies (distinct terms read as 1 on both paths), and a positional field throws "cannot index positions while using custom TermFrequencyAttribute" (:210-216).
  2. MemoryIndex does not. storeTerms (MemoryIndex.java:991-1053) asks the stream only for the term, position-increment, offset and payload attributes (:1002-1007) and per token does info.numTokens++ (:1012), info.sliceArray.freq[ord]++ (:1025) and info.sumTotalTermFreq++ (:1027). The norm is computed from numTokens (:1380-1393). TermFrequencyAttribute appears nowhere in the file.
  3. Frequency 1 decodes to almost nothing. Float.intBitsToFloat(1 << 15) is 4.5918E-41, a subnormal float below the smallest value FeatureField accepts (Float.MIN_NORMAL, FeatureField.java:162-171). The linear function returns it. The other functions depend on their parameters (executed): saturation weight * (1 - pivot / (f + pivot)) (:450) with the pivot 10 rounds 10f / (10f + 4.5918E-41f) to 1.0 and returns exactly 0.0, and so does sigmoid with the pivot 10 (:531); log with scalingFactor 1 (:356) is always 0.0, because 1f + 4.5918E-41f is 1f. Saturation without a pivot takes the pivot from the index statistics (:408-415, :725-738), which on a one-document MemoryIndex decode to the same 4.5918E-41, so it scores 0.5 as on IndexWriter; tiny explicit pivots give positive but wrong scores.
  4. 0.0 is "no match". MemoryIndex.search returns "0.0 indicating no match" (MemoryIndex.java:1105-1106; the class example tests score > 0.0f, :153-157), and Monitor's ScoringMatch reports a match only when score > 0 (ScoringMatch.java:37). That convention was already imperfect: a FILTER-only query and a boost-0 query match but score 0.0 on both paths (executed); this bug adds matching documents that score 0.0 only because their frequency was dropped.
  5. The hit set is unchanged for the tested queries. Every term is still indexed for the document. The follow-up's probe ran 12 query shapes (five FeatureField function settings, two term queries, FILTER and two MUST_NOT combinations, two zero-score controls): IndexSearcher.count was the same on both paths for all 12 (executed). A query that itself depends on frequencies could still differ; none was tested.
Exposure and likelihood232 words
  • Monitor. With ScoringMatch and FeatureField queries, a single document scores differently from the same document in a batch (10.0 vs 4.5918E-41), and a stored query whose only scoring parts round to 0.0 (saturation or sigmoid with an explicit pivot such as 10, log with scalingFactor 1) is not reported at all for a single document (executed: saturation on main and 10.5.2; sigmoid and log in the fact checks' Monitor runs on main). The no-pivot saturation query keeps its match (0.5 on both paths). Monitor matches a single document on MemoryIndex only since 8.6.0 (LUCENE-9314): in 8.2.0 to 8.5.2 match(Document) went through the IndexWriter-based batch (read).
  • Direct MemoryIndex.search. Its javadoc makes score > 0.0f the match test; the same query shapes report "no match" for a matching document (executed).
  • Custom-frequency text fields. Postings frequency, totalTermFreq, sumTotalTermFreq, the norm and BM25 scores are wrong for DOCS_AND_FREQS fields fed by DelimitedTermFrequencyTokenFilter or a custom stream, and for DOCS_AND_CUSTOM_FREQS fields (10.5.0+) (executed).
  • Default vs opt-in. Default MemoryIndex, Monitor and similarity settings; the trigger is the opt-in custom-frequency feature.
  • Scale. Any document, deterministic, every time.
  • Silent. No exception, and for the tested queries no change in which documents match, so only code that reads frequencies, term statistics, norms or scores notices (including score-based match tests); no report was found in the nine years since 7.0.0, so the registry rates the likelihood "rare".

Minimal repro

The test shows that MemoryIndex ignores TermFrequencyAttribute, so a FeatureField("f", "pr", 10f) is indexed with frequency 1 and FeatureField.newLinearQuery("f", "pr", 1f) scores it wrongly.

TestMemoryIndexFeatureField.java (JUnit test)30 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 org.apache.lucene.document.FeatureField;
import org.apache.lucene.tests.util.LuceneTestCase;

public class TestMemoryIndexFeatureField extends LuceneTestCase {

  public void testLinearQueryScoresFeatureValue() {
    MemoryIndex mi = new MemoryIndex();
    mi.addField(new FeatureField("f", "pr", 10f), null);
    // expected: the feature value 10.0 (as with IndexWriter); actual: 4.5918E-41 (frequency 1)
    assertEquals(10f, mi.search(FeatureField.newLinearQuery("f", "pr", 1f)), 0f);
  }
}
Command
# in a copy of this finding's repro folder (repro/LFV-333/, which holds src/)
# Lucene checkout at the tested commit, built with: ./gradlew --no-daemon assemble   (JDK 25)
LUCENE=/path/to/lucene; V=12.0.0-SNAPSHOT
CP=$LUCENE/lucene/core/build/libs/lucene-core-$V.jar
CP=$CP:$LUCENE/lucene/codecs/build/libs/lucene-codecs-$V.jar
CP=$CP:$LUCENE/lucene/test-framework/build/libs/lucene-test-framework-$V.jar
CP=$CP:$LUCENE/lucene/memory/build/libs/lucene-memory-$V.jar
CP=$CP:randomizedtesting-runner-2.9.2.jar:junit-4.13.2.jar:hamcrest-3.0.jar   # test-framework dependencies (Maven Central)
CP=$CP:junit-platform-console-standalone-6.1.3.jar   # JUnit Platform console launcher (Maven Central)

javac -d classes -cp "$CP" $(find src -name '*.java')

# run 1: assertions on, seed DEADBEEF
java -ea -esa --add-modules jdk.incubator.vector --enable-native-access=ALL-UNNAMED -Dtests.seed=DEADBEEF \
     -cp "classes:$CP" org.junit.platform.console.ConsoleLauncher \
     execute --select-class org.apache.lucene.index.memory.TestMemoryIndexFeatureField
Expected vs actual
Expected: mi.search(...) returns the feature value 10.0 (as a one-document IndexWriter index does). Actual: 4.5918E-41.

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

More files, environment, full output34 words

Environment

luceneLucene main e1faeba08bde (2026-11-02)
jdk25.0.3+9-LTS

Observed output

# Lucene main 645d4fe14a20, assertions on (-ea), seed DEADBEEF: 1 test(s) run, 1 failed
    => java.lang.AssertionError: expected:<10.0> but was:<4.5918E-41>
       org.junit.Assert.fail(Assert.java:89)
       org.junit.Assert.failNotEquals(Assert.java:835)
       org.junit.Assert.assertEquals(Assert.java:577)
       org.junit.Assert.assertEquals(Assert.java:701)
       org.apache.lucene.index.memory.TestMemoryIndexFeatureField.testLinearQueryScoresFeatureValue(TestMemoryIndexFeatureField.java:28)

Why it looks unintended

  • TermFrequencyAttribute sets "the custom term frequency of a term within one document"; FeatureField values "are internally encoded as term frequencies". MemoryIndex accepts both and drops the number.
  • MemoryIndex is a documented "substitute" for an index; its duel test checks norms, hit counts and term-vector frequencies against IndexWriter.
  • No limitation is documented; ignoring omitNorms was fixed as a bug (LUCENE-7679).
The full argument361 words
  • The attribute's contract. TermFrequencyAttribute.java:24-26: "Sets the custom term frequency of a term within one document. If this attribute is present in your analysis chain for a given field, that field must be indexed with IndexOptions#DOCS_AND_FREQS." FeatureField and the DOCS_AND_FREQS repro field meet that precondition word for word. DOCS_AND_CUSTOM_FREQS (10.5.0) is newer than that sentence and not named in it; IndexOptions.java:53-54 calls it "Like DOCS_AND_FREQS except the "custom freqs" are treated as scores", and IndexWriter honours the attribute for it, so it is a parity case. DelimitedTermFrequencyTokenFilter states the same precondition: "DOCS_AND_FREQS but no positions or offsets" (DelimitedTermFrequencyTokenFilter.java:31-32).
  • FeatureField's contract. FeatureField.java:47: "Feature values are internally encoded as term frequencies." Its query functions read the value back from the frequency.
  • MemoryIndex's role. MemoryIndex.java:106: "a replacement/substitute for RAM-resident Directory implementations"; :129: "Arbitrary Lucene queries can be run against this class". Monitor swaps it in for a one-document IndexWriter index only for speed (DocumentBatch.java:101-102: "MemoryIndex benchmarks as better performing than RAMDirectory for this case"), since 8.6.0, when LUCENE-9314 fixed "Single-document monitor runs were using the less efficient MultiDocumentBatch implementation" (CHANGES.txt:5804-5805).
  • Tests. TestMemoryIndexAgainstDirectory duels MemoryIndex against an IndexWriter index: equal norms (:200-207), equal hit counts (:259-268), and in testDuelMemoryIndexCoreDirectoryWithArrayField equal term-vector frequencies, positions and offsets (:714-800). Its postings and sumTotalTermFreq checks (:210-250) compare the MemoryIndex reader with its own term vectors, not with IndexWriter (:193-196). Its analyzers never set the attribute. No test in lucene/memory or lucene/monitor uses TermFrequencyAttribute or FeatureField, and none asserts frequency 1.
  • History. LUCENE-7854 (d276acfbbcb8, 2017) added the attribute in 14 files, none in lucene/memory; #15584 (DOCS_AND_CUSTOM_FREQS, 10.5.0) changed one MemoryIndex line without adding support. Neither discussion mentions MemoryIndex or Monitor.
  • Precedents. LUCENE-7679 / #8730 (type:bug, Bug Fixes in 6.5.0, CHANGES.txt:8442-8443): "MemoryIndex was ignoring omitNorms settings on passed-in IndexableFields", which also made its scores differ from IndexWriter's. LUCENE-8756 / #9801 (type:bug): "MLT queries ignore custom term frequencies", the same omission in MoreLikeThis; 8.1.0's CHANGES lists the fix under "Changes in Runtime Behavior" (CHANGES.txt:6564, entry :6578-6579).
The devil's-advocate check200 words

The strongest works-as-designed case, built by the verifiers and a reading-only review by a model from another family (rated "moderate"): MemoryIndex does not mirror index options. createFieldInfo always declares DOCS_AND_FREQS_AND_POSITIONS[_AND_OFFSETS] (MemoryIndex.java:759-763), TestMemoryIndex.testKeywordWithoutTokenStream (:1093) accepts occurrence counts with positions for DOCS-only keyword fields, and IndexWriter rejects custom frequencies together with positions ("cannot index positions while using custom TermFrequencyAttribute", FreqProxTermsWriterPerField.java:208-219). The attribute's javadoc names only DOCS_AND_FREQS, and Monitor's use of MemoryIndex is a speed optimization, not a stated promise of full equivalence. So custom frequencies may never have been part of MemoryIndex's model.

Why it fails: MemoryIndex accepts these stock fields without complaint and then returns wrong scores, including a documented "no match" for a matching document. The caller met the attribute's precondition; it is MemoryIndex that overrides the index options. Nothing in javadoc, CHANGES, MIGRATE or tests documents a limitation (compare GITHUB#15334, whose CHANGES entry says "MemoryIndex does not support nearest neighbor search"). Honouring the frequency or rejecting the field would both remove the silent wrong score. The argument does hold for addField(String, TokenStream), which always indexes positions (it uses defaultFieldType, MemoryIndex.java:476-479, :728; its javadoc does not say so).

Suggested fix

Recommended Honour the frequency where IndexWriter does, reject overflow as IndexWriter does

In MemoryIndex.storeTerms, read TermFrequencyAttribute on DOCS_AND_FREQS and DOCS_AND_CUSTOM_FREQS fields (where IndexWriter honours it), add it to the frequency, sumTotalTermFreq and field length, and reject int overflow with IndexWriter's "too many tokens for field" (+40/-8, one file). The repro passes with assertions on and off, and the whole memory, monitor and highlighter test suites pass (73 classes, 991 tests).

final boolean customFreqs =
    indexOptions == IndexOptions.DOCS_AND_FREQS || indexOptions == IndexOptions.DOCS_AND_CUSTOM_FREQS;
final boolean termDoc = indexOptions == IndexOptions.DOCS_AND_CUSTOM_FREQS;
TermFrequencyAttribute termFreqAtt =
    customFreqs ? stream.addAttribute(TermFrequencyAttribute.class) : null;
// per token:
final int termFreq = termFreqAtt == null ? 1 : termFreqAtt.getTermFrequency();
info.numTokens = addTokens(info, info.numTokens, termDoc ? 1 : termFreq); // was ++
info.sliceArray.freq[ord] = addTokens(info, info.sliceArray.freq[ord], termFreq); // was ++
info.sumTotalTermFreq += termFreq; // was ++
All options and trade-offs4 options · 906 words

One fix was applied in a scratch worktree at a9779ef3c193 and run through gradle (seed DEADBEEF); the other options were not run. The hunt's first sketch (no overflow check) is listed last because it must not be used as is.

Honour the frequency where IndexWriter does, reject overflow as IndexWriter does (validated by the checks) (recommended)

storeTerms gets the field type's IndexOptions. For DOCS_AND_FREQS and DOCS_AND_CUSTOM_FREQS it reads TermFrequencyAttribute and adds the frequency to the postings frequency, maxTermFrequency, sumTotalTermFreq and the field length (1 per term for DOCS_AND_CUSTOM_FREQS, as IndexingChain does). Positional field types, including every addField(String, ...) path and the highlighters' re-analysis, keep today's one-per-token counting. Int sums go through Math.addExact and throw IllegalArgumentException("too many tokens for field ...") like IndexingChain. MemoryPostingsEnum.nextPosition returns -1 once the stored positions run out (a frequency of 33344 has one stored position), which is what PostingsEnum returns when positions are not indexed. A second diff adds the repro tests plus testCustomFrequencyOverflowIsRejected.

final boolean customFreqs =
    indexOptions == IndexOptions.DOCS_AND_FREQS || indexOptions == IndexOptions.DOCS_AND_CUSTOM_FREQS;
final boolean termDoc = indexOptions == IndexOptions.DOCS_AND_CUSTOM_FREQS;
TermFrequencyAttribute termFreqAtt =
    customFreqs ? stream.addAttribute(TermFrequencyAttribute.class) : null;
// per token:
final int termFreq = termFreqAtt == null ? 1 : termFreqAtt.getTermFrequency();
info.numTokens = addTokens(info, info.numTokens, termDoc ? 1 : termFreq); // was ++
info.sliceArray.freq[ord] = addTokens(info, info.sliceArray.freq[ord], termFreq); // was ++
info.sumTotalTermFreq += termFreq; // was ++

Trade-offs

  • The positional view stays inconsistent. MemoryIndex still declares positions (hasPositions true) while a FeatureField posting now has frequency 33344 and one stored position: later positions read as -1, term vectors report frequency 33344, and toStringDebug prints "positions=33344" next to one real position (executed). -1 is what PostingsEnum returns when positions are not indexed, but this reader says they are, so a positional query on a FeatureField runs instead of throwing as on an IndexWriter index. Making createFieldInfo follow the field type would fix it, but changes what testKeywordWithoutTokenStream asserts.
  • Not validated for position consumers. No test runs a phrase, span or other positional query, or CheckIndex, against a fixed custom-frequency field; apart from this doc's own tests the upstream memory and monitor suites have no FeatureField or TermFrequencyAttribute case. A PR should add such tests, or stop advertising positions for these fields.
  • Positional fields stay lenient. A TextField analysed with DelimitedTermFrequencyTokenFilter makes IndexWriter throw "cannot index positions while using custom TermFrequencyAttribute"; MemoryIndex keeps counting 1. Throwing there too could break highlighting of such fields, which re-analyses text into a MemoryIndex through addField(String, TokenStream) (MemoryIndexOffsetStrategy.java:112, WeightedSpanTermExtractor.java:411-413).
  • DOCS fields stay lenient. For a repeated term with a frequency other than 1, IndexWriter throws "must index term freq while using custom TermFrequencyAttribute" (FreqProxTermsWriterPerField.java:147-152); the fix does not read the attribute for DOCS, so MemoryIndex accepts it.
  • No duplicate-term check for DOCS_AND_CUSTOM_FREQS. IndexWriter rejects the document with "Document update skipped due to duplicate termdoc term" (IndexingChain.java:2095-2097, caused by "has duplicate term" at FreqProxTermsWriterPerField.java:193-194); MemoryIndex adds the two frequencies (executed). The hunt's harness counted 432 such cases; a separate parity gap that can go in the same PR.
  • The overflow exception leaves the MemoryIndex partly updated, like any exception in the middle of a TokenStream (IndexWriter marks the document deleted). Not benchmarked: per token one attribute read and two checked additions.

What the checks validated

  • Stock main a9779ef3c193: the memory repro fails 4 of 5 and the Monitor test 2 of 2, with assertions on and off; the control passes.
  • With the fix: the 5 memory tests plus testCustomFrequencyOverflowIsRejected and the 2 Monitor tests pass, with assertions on and with assertions off.
  • With the fix, all of lucene/memory, lucene/monitor and lucene/highlighter and core's TestFeatureField and TestCustomTermFreq pass: 73 classes, 991 tests, 1 skipped, 0 failures.
  • ecjLint and checkGoogleJavaFormat pass for memory and monitor, and forbiddenApis for memory, with the tests in place.
  • Through the classpath runner with the fixed MemoryIndex.java shadowed, the finder's repro (3 of 3) and the independent reproducer's (7 of 7) pass; the follow-up's input of 50,000 tokens with frequency Integer.MAX_VALUE, which made the field vanish under the hunt's first sketch, now throws "too many tokens for field".

Reject custom frequencies

Throw IllegalArgumentException when a token's frequency is not 1 ("MemoryIndex does not support custom term frequencies"). Not run.

Trade-offs

Small and removes the silent wrong score, but every document with a FeatureField that goes through a MemoryIndex (a single-document Monitor match, the percolator path) would start failing, even when no query reads the field; Monitor batches of two or more use IndexWriter and are not affected. Given MemoryIndex's always-positional design, maintainers may well prefer this option.

Document the limitation

Say in the MemoryIndex class javadoc that custom term frequencies are counted as 1. (GITHUB#15334 stated its kNN limitation only in its CHANGES entry, CHANGES.txt:1476-1477; MemoryIndex.java has no such javadoc.) Not run.

Trade-offs

Makes the behaviour a documented limitation, but leaves the wrong scores and the "no match" for matching documents in place.

The hunt's first sketch (do not use as is)

Reads the attribute for every field type and adds it with plain +=. It passes the repros and the 51 upstream memory tests.

Trade-offs

With 50,000 tokens of frequency Integer.MAX_VALUE the field length wraps and the field vanishes from the reader (MemoryIndex hides fields with numTokens <= 0, MemoryIndex.java:1834, 1842), executed; with the recommended fix the same input throws "too many tokens for field".

Downstream note

Downstream percolators that build a MemoryIndex from the document, such as Elasticsearch's and OpenSearch's with rank_feature (a FeatureField), score such queries wrong (by code reading); they decide matches by hit existence, so the matched queries do not change.

How it was found

Bounded-exhaustive translation validation on the real jar: the same tiny document (every stream of up to two tokens with custom frequencies 1, 2 and 7, plus FeatureField with four values and four query functions, 1,836 cases) built once with IndexWriter and once with MemoryIndex, then compared through the public API. Every case with a frequency other than 1 differed.

The method in full162 words

Method: bounded-exhaustive translation validation on the stock jar (semi-formal tier), in a hunt unit on MemoryIndex equivalence.

  • The harness builds each case twice, with a one-document IndexWriter index and with MemoryIndex, and compares the postings frequency, totalTermFreq, sumTotalTermFreq, the norm and query scores through LeafReader and the query API. Scope CF: one field, DOCS_AND_FREQS and DOCS_AND_CUSTOM_FREQS, omitNorms on and off, every stream of at most two tokens over {a, b} with custom frequencies {1, 2, 7}, plus FeatureField with 4 values and 4 query functions: 1,836 cases, 4,220 comparisons.
  • Mismatches in every class (for example 1,120 DOCS_AND_FREQS and 800 DOCS_AND_CUSTOM_FREQS score mismatches, and all 16 FeatureField query scores); 0 mismatches with the fix sketch shadowed.
  • Found 2026-10-26 on main ea57360912ed; then turned into public-API JUnits and checked by an independent reproducer, an intended-behaviour devil's advocate, reading-only reviews by an independent reviewer from another model family (OpenAI GPT-5.6), an adjudication and a follow-up with history and prior-report searches.

Prior reports, history and checks

Prior reports

No prior report found (2026-10-26) in 152 recorded queries over GitHub, the mailing lists, JIRA, Elasticsearch, OpenSearch, Solr and the web, plus the changed files of all 411 open PRs. Closest: LUCENE-8756 (MoreLikeThis ignored custom frequencies) and LUCENE-7679 (MemoryIndex ignored omitNorms).

Searches4 searches

Verdict new: no hit is a report of this bug by anyone. Keyword search cannot prove absence: a report that never names MemoryIndex (for example odd rank_feature scores in a percolator) could be missed, though the downstream percolator searches were aimed at that case.

What was searched, when, and what turned up
WhereWhenHowFound
apache/lucene issues and PRs (GitHub search, migrated JIRA included); Elasticsearch, OpenSearch, Solr2026-10-2634 issue/PR searches (7 also over downstream repositories) plus 2 further downstream queries: MemoryIndex with term frequency, TermFrequencyAttribute, FeatureField, custom term, rank feature, DelimitedTermFrequencyTokenFilter, DOCS_AND_CUSTOM_FREQS; storeTerms; percolator rank_featureno report; related only: LUCENE-8756 / #9801, LUCENE-7679 / #8730, #15584, #11799
apache/lucene commits and git history of lucene/memory and lucene/monitor2026-10-263 commit searches; git log -S TermFrequencyAttribute / getTermFrequency; all 40 MemoryIndex commits; CHANGES.txtMemoryIndex has never read the attribute; no fix
dev@, java-user@, issues@, commits@ (full archive); JIRA archive (LUCENE, SOLR)2026-10-2660 list queries; 19 JIRA queries, 13 issues read in full (as listed in the prior-report record)no report; SOLR-10828 (Solr PreAnalyzedField lacks TermFrequencyAttribute support, open) is the same class elsewhere
open apache/lucene PRs; luwak issues; the web2026-10-26changed files of all 411 open PRs; all 191 luwak issue titles; 25 web queries7 open PRs touch MemoryIndex.java (#486, #13342, #13525, #13948, #14475, #14716, #16847), none changes storeTerms; luwak #135 is the omitNorms report behind LUCENE-7679

History

An omission since custom frequencies exist: LUCENE-7854 (d276acfbbcb8, 2017-06-06; first release 7.0.0) taught IndexWriter the attribute but not MemoryIndex. FeatureField (7.4.0) and DOCS_AND_CUSTOM_FREQS (10.5.0) widened the reach. Ten releases from 7.0.0 to 10.5.2 and main were run: all fail; not fixed upstream.

Versions tested10 rows

Introduced by d276acfbbcb8 (2017-06-06), "LUCENE-7854: enable indexing custom term frequencies" (@mikemccand; GitHub issue #8905; committed from a JIRA patch). It added TermFrequencyAttribute and made DefaultIndexingChain, FreqProxTermsWriterPerField and TermVectorsConsumerPerField honour it, in 14 files, none in lucene/memory. The one-per-token counting in storeTerms is older (current lines from LUCENE-7091, cf3eea264066, 2016), so this is an omission, not a regression.

Later commits widened the reach without touching it: 5844ed4ac953 (DelimitedTermFrequencyTokenFilter, 7.0.0), LUCENE-8197 (FeatureField, 7.4.0; main commit 710993435f36, backport 70cc7b68783), LUCENE-8031 (29e5b8abcee8, edited storeTerms, 8.0.0) and #15584 (DOCS_AND_CUSTOM_FREQS, 10.5.0; main commit 5158a52a082b, backport 3a55cc9ac5e; one MemoryIndex line changed). Lucene Monitor arrived in 8.2.0, but its single-document match uses MemoryIndex only since 8.6.0 (LUCENE-9314, 633ae90d7d9): in 8.2.0 to 8.5.2 match(Document) went through match(new Document[] {doc}) and the IndexWriter-based MultiDocumentBatch (read). 10.6.0 was released on 2026-10-29; on branch_10_6 (2763db11b0a, 2026-10-24) storeTerms still counts one per token (source only, not run). MemoryIndex.java never mentions the attribute at any of the 65 local release tags 7.0.0 to 10.5.2; the local clone has no tags for 8.8.2 to 8.11.3, which were neither read nor run (8.8.1 and 8.11.4 on either side are affected).

Versions and branches executed, with results
Version or commitVerdictResult
main a9779ef3c193 affectedthis doc's JUnits: 6 of 7 fail through gradle, assertions on and off; the control passes
main ea57360912ed affectedthe finder's test 3 of 3, the independent test 6 of 7; -ea and -da
10.5.2 affectedthis doc's JUnits 6 of 7 fail (control passes); the finder's, the independent and the Monitor tests and the history probes fail
10.5.0 DOCS_AND_CUSTOM_FREQS arrivesthe DOCS_AND_CUSTOM_FREQS JUnit fails (sumTotalTermFreq 7 vs 1); 10.4.0 has no such option
9.0.0, 9.12.3, 10.0.0 affectedhistory probes fail (custom stream, FeatureField saturation and log); on 9.12.3 the finder's JUnit 2 of 2 and the independent one 4 of 5
8.0.0, 8.11.4 affectedhistory probes fail; BM25 0.24558227 vs 0.13076457 from 8.0.0
7.4.0, 7.7.3 FeatureField arrivesFeatureField freq 33344 vs 1, saturation 0.5 vs 0.0, log 2.3978953 vs 0.0
7.0.0 first releasecustom stream freq 7 vs 1, sumTotalTermFreq 8 vs 2, norm 8 vs 2; DelimitedTermFrequencyTokenFilter fails
6.6.6 no APIcompile error: no TermFrequencyAttribute
fix (scratch worktree at a9779ef3c193) not affectedthis doc's JUnits pass with assertions on and off; memory, monitor and highlighter suites pass (73 classes, 991 tests)

Independent checks

An independent reproducer and an intended-behaviour devil's advocate reproduced it with their own tests, controls passing. Reading-only reviews by a model from another family (OpenAI GPT-5.6) agreed on the root cause; an adjudication confirmed it, and this doc's gradle runs reproduce it on a9779ef3c193 and validate the fix.

Checks and verdicts394 words

Every executing check wrote or re-ran its own JUnit on stock main; every control passed.

Each independent check and its verdict
CheckVerdictWhat it found
finder candidateTranslation-validation harness plus public-API JUnits: 3 of 3 fail on main (-ea, -da), 2 of 2 on 10.5.2 and 9.12.3; with the first fix sketch they pass and 51 upstream memory tests pass.
independent reproducer realOwn JUnits written from the claim: 6 of 7 fail on main (control passes), 5 of 6 on 10.5.2, 4 of 5 on 9.12.3, a main() variant on 8.11.4; found the saturation 0.0 and the Monitor batch-size symptom.
cross-model reviews (refute, intended) not real (low, procedural); real (medium)An independent reviewer from another model family (OpenAI GPT-5.6) could only read code. The refute lens agreed on the root cause and that the inputs are legal; its "not real" was procedural. The intended lens rated the always-positional argument moderate and rejected it. A separate run re-ran the repro: 3 of 3 fail.
intended-behaviour devil's advocate realBuilt the strongest intended reading and showed why it fails; its own Monitor batch-size test fails on main (-ea, -da) and 10.5.2.
adjudication confirmed, highRe-ran the independent reproduction's test (6 of 7, -ea and -da); corrected the DOCS_AND_CUSTOM_FREQS sub-case to released since 10.5.0.
Follow-up and main refresh reproduces; keepHistory probes on 14 targets from 6.6.6 to main, the security probe, 152 prior-report queries, re-runs; the refresh to a9779ef3c193 gives identical failures.
this doc's gradle runs reproduces; fix validatedStock a9779ef3c193: 6 of 7 fail through gradle, assertions on and off, control passing; with the fix 8 of 8 pass (with the overflow test) and 73 nearby classes (991 tests) pass; lint and format pass.
fact checks of this doc (two, 2026-10-27) bug, mechanism and fix hold; text correctedAn independent fact check re-ran the gradle repro (4 of 5 and 2 of 2 fail, assertions on and off) and the fix (8 of 8; 73 classes pass), and found two citation errors. A second check by an independent reviewer from another model family (OpenAI GPT-5.6, reading only, on reference outputs and a probe made for it) found that Monitor's single-document path dates from 8.6.0, not 8.2.0, that the zero scores depend on the function parameters, and several overclaims. All were corrected in this doc.

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,424 words

Finding record

LFV-333, snapshot of 2026-11-06

Registry title
MemoryIndex ignores TermFrequencyAttribute, so FeatureField values and custom term frequencies are indexed as frequency 1 and their postings frequencies, term statistics, norms and scores differ from IndexWriter's (Lucene Monitor's single-document matching included)
Area
memory / MemoryIndex term inversion (custom term frequencies: FeatureField, TermFrequencyAttribute streams, DOCS_AND_CUSTOM_FREQS); reached in-tree through monitor single-document batches
Priority
Medium, rank 239: Since 7.0, MemoryIndex ignores custom term frequencies, so FeatureField scores collapse while matching is unchanged for the tested queries, a silent scoring error like the norms row LFV-105, which it follows.
Impact class
silent wrong results
Impact note
Anyone who puts a FeatureField (rank feature) or another field whose TokenStream sets TermFrequencyAttribute (a DOCS_AND_FREQS custom-frequency stream, DelimitedTermFrequencyTokenFilter input, or since 10.5.0 a DOCS_AND_CUSTOM_FREQS field) into a MemoryIndex, directly, through fromDocument/addField(IndexableField, Analyzer) or through Lucene Monitor's one-document batches (since 8.6.0), counts each such token as frequency 1, so FeatureField query scores collapse (linear 10.0 becomes 4.5918E-41; saturation or sigmoid with an explicit pivot such as 10, and log with scalingFactor 1, become exactly 0.0, which MemoryIndex.search documents as 'no match' and which makes Monitor's ScoringMatch drop the match) and term statistics, norms and BM25 scores of custom-frequency fields are wrong; for the tested queries, which documents match is unchanged; default settings otherwise, opt-in custom-frequency feature, any scale, every release checked since 7.0.0.
Summary
You add a document with FeatureField("features", "pagerank", 10f), or a tokenized DOCS_AND_FREQS field whose TokenStream sets TermFrequencyAttribute (for example DelimitedTermFrequencyTokenFilter on 'a|7'), to a MemoryIndex and expect its postings, statistics and scores to equal those of the same document in a one-document IndexWriter index, as they do for ordinary fields. Instead MemoryIndex.storeTerms never reads the attribute and counts every token as frequency 1: FeatureField.newLinearQuery scores 4.5918E-41 instead of 10.0, newSaturationQuery with the pivot 10 scores exactly 0.0 (MemoryIndex.search's 'no match' value) for a document that matches, and a term with custom frequency 7 reports freq 1, sumTotalTermFreq 1 and a lower BM25 score. Lucene Monitor inherits it (since 8.6.0): the same FeatureField document scores 10.0 in a two-document batch (IndexWriter path) and 4.5918E-41 when matched alone (MemoryIndex path).
Introduced
d276acfbbcb8ea6124fe486e35cea9eb928e4702 (LUCENE-7854 'enable indexing custom term frequencies', by GitHub mikemccand; a JIRA patch with no GitHub PR, migrated as apache/lucene#8905) [read: it added TermFrequencyAttribute and made the IndexWriter chain (DefaultIndexingChain, FreqProxTermsWriterPerField, TermVectorsConsumerPerField) honour it, in 14 files, none in lucene/memory; exec: the history probe does not compile on 6.6.6 (no TermFrequencyAttribute) and fails on 7.0.0]. An omission since the feature was introduced, not a regression: MemoryIndex's one-per-token counting is older (current lines from LUCENE-7091 cf3eea264066, 2016-03-09; a per-token loop since 2005). Later commits widened the reach without touching it: 5844ed4ac953 (LUCENE-7854 follow-up, DelimitedTermFrequencyTokenFilter, 7.0.0), 70cc7b68783b / 710993435f36 (LUCENE-8197, FeatureField, 7.4.0), 29e5b8abcee8 (LUCENE-8031, edited storeTerms and the norm, still not reading the attribute, 8.0.0) and 3a55cc9ac5e9 / 5158a52a082b (#15584, DOCS_AND_CUSTOM_FREQS, one MemoryIndex line changed, 10.5.0) (2017-06-06 (d276acfbbcb8); reach widened 2017-06-09 (DelimitedTermFrequencyTokenFilter), 2018-03-20 (FeatureField) and 2026-04-24 (#15584 merged, DOCS_AND_CUSTOM_FREQS))
First affected version
7.0.0 [exec] (first release with TermFrequencyAttribute; 6.6.6 has no such API and the probe does not compile); the FeatureField path from 7.4.0 [exec; 7.3.1 has no FeatureField]; the DOCS_AND_CUSTOM_FREQS sub-case from 10.5.0 [exec; the option is absent in 10.4.0]
Affected versions
Every release checked from 7.0.0 to 10.6.0 (10.6.0 by reading), and main (ea57360912ed and a9779ef3c193). [exec] plain main() history probes (a DOCS_AND_FREQS custom-frequency stream; FeatureField with saturation and log queries; DelimitedTermFrequencyTokenFilter) fail on 7.0.0, 7.4.0, 7.7.3, 8.0.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2 and main, each with a passing frequency-1 control, and pass on main with the fix sketch shadowed; the finder's JUnit fails on main (3 of 3, -ea and -da), 10.5.2 and 9.12.3 (2 of 2) and a main() variant on 8.11.4; the DOCS_AND_CUSTOM_FREQS JUnit fails on 10.5.0, 10.5.2 and main; the independent reproduction's JUnit fails on main (6 of 7, -ea and -da), 10.5.2 (5 of 6) and 9.12.3 (4 of 5) and its main() variant on 8.11.4; the Monitor batch-size test fails on main (-ea and -da) and 10.5.2. [exec] on main a9779ef3c193 the bug doc's JUnits fail 6 of 7 through gradle, assertions on and off. [read] the releases not executed (7.1-7.3, 8.1-8.8.1, 9.1-9.11, 10.1-10.4): MemoryIndex.java never mentions the attribute at any of the 65 local release tags 7.0.0 to 10.5.2; the local history clone has no tags for 8.8.2 to 8.11.3, which were neither read nor run; Lucene Monitor (from 8.2.0) matches a single document on MemoryIndex only since 8.6.0 (LUCENE-9314): in 8.2.0 to 8.5.2 Monitor.match(Document) called match(new Document[] {doc}) and DocumentBatch.of(Analyzer, Document...) always built the IndexWriter-based MultiDocumentBatch. branch_10_6 (2763db11b0a, 2026-10-24, before the 10.6.0 release of 2026-10-29): storeTerms unchanged, read only. Not checked: x86. Unfixed: no commit in lucene/memory or lucene/monitor adds custom-frequency support, and no open PR changes storeTerms
Status
documented (brief, checks and a per-finding doc exist)
Verification
Confirmed by AI-run checks: an independent repro, a cross-model review that could only read the code (its low-confidence “not real” is procedural), 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 (2026-10-26, the prior-report record, verdict new: 152 read-only queries: 34 GitHub issue/PR searches (7 also over Elasticsearch, OpenSearch and Solr repositories) plus 2 further downstream-repository queries, 3 commit searches, 60 mailing-list queries over dev@, java-user@, issues@ and commits@, 19 JIRA-archive queries over LUCENE and SOLR (13 issues read in full, as listed in the prior-report record), 25 web queries, GitHub thread reads, the changed-file lists of all 411 open apache/lucene PRs, git history of lucene/memory and lucene/monitor, CHANGES.txt and the luwak issue titles); not fixed upstream: no commit in lucene/memory or lucene/monitor adds custom-frequency support, and the 7 open PRs that touch MemoryIndex.java (#486, #13342, #13525, #13948, #14475, #14716, #16847) do not change storeTerms or frequencies. Related items, not reports of this bug: LUCENE-8756 / #9801 (MoreLikeThis ignored custom term frequencies; same bug class, other component, fixed in 8.1.0), LUCENE-7679 / #8730 and luwak #135 (MemoryIndex ignored omitNorms and gave wrong scores; fixed in 6.5.0), SOLR-10828 (Solr PreAnalyzedField lacks TermFrequencyAttribute support, open), LUCENE-7854 / #8905 (introduced the attribute), LUCENE-8197 / #9244 (FeatureField), LUCENE-7866, #15584, #11086, #9547, #9990, #11799, LUCENE-9314, LUCENE-4880 / #5945, elastic/elasticsearch #28803
Lucene commit tested
main a9779ef3c193 (commit of 2026-10-26): the two JUnits through gradle in a clean worktree, stock and with the fix, assertions on and off, plus the whole memory, monitor and highlighter test suites with the fix (2026-10-27); the same JUnits through the hunt's classpath runner on main and the 10.5.2 release jars. Hunt runs on main ea57360912ed and on releases from 7.0.0 to 10.5.2; the main refresh to a9779ef3c193 reproduces with identical messages.

Lucene and upstream sources

Finding record
  • a Lucene main checkout lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:991-1050 (storeTerms: numTokens++, freq[ord]++, sumTotalTermFreq++ per token; no TermFrequencyAttribute anywhere in the file), :1100-1108 (search javadoc: 0.0 indicating no match)
  • a Lucene main checkout lucene/core/src/java/org/apache/lucene/index/FreqProxTermsWriterPerField.java:208-218 (getTermFreq) and lucene/core/src/java/org/apache/lucene/index/IndexingChain.java:1997-2008 (field length adds getTermFrequency(), or 1 for term-doc fields, with Math.addExact)
  • a Lucene main checkout lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/TermFrequencyAttribute.java:23-35 (contract) and lucene/core/src/java/org/apache/lucene/document/FeatureField.java (values encoded as term frequencies)
  • a Lucene main checkout lucene/monitor/src/java/org/apache/lucene/monitor/DocumentBatch.java:44-62 (one document -> SingletonDocumentBatch on MemoryIndex, more -> IndexWriter) and ScoringMatch.java:34-39 (a match needs score > 0)
The finding in 30 seconds
  • lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:106, 153-157, 991-1053 (1002-1007, 1012, 1025, 1027), 1105-1106, 1380-1393
  • lucene/core/src/java/org/apache/lucene/document/FeatureField.java:47, 114, 162-171, 185-186, 222, 240-249, 356, 408-415, 450, 531, 725-738
  • lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/TermFrequencyAttribute.java:24-26
  • lucene/core/src/java/org/apache/lucene/index/FreqProxTermsWriterPerField.java:208-219 (210-216: positions); lucene/core/src/java/org/apache/lucene/index/IndexingChain.java:2053-2064
  • lucene/monitor/src/java/org/apache/lucene/monitor/DocumentBatch.java:44-62, 105-113; ScoringMatch.java:37
Why it looks unintended
  • lucene/core/src/java/org/apache/lucene/analysis/tokenattributes/TermFrequencyAttribute.java:24-26; lucene/core/src/java/org/apache/lucene/document/FeatureField.java:47; lucene/core/src/java/org/apache/lucene/index/IndexOptions.java:53-56; lucene/analysis/common/src/java/org/apache/lucene/analysis/miscellaneous/DelimitedTermFrequencyTokenFilter.java:29-32
  • lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java:106, 129, 476-479, 692-730 (728), 759-763; lucene/monitor/src/java/org/apache/lucene/monitor/DocumentBatch.java:101-102
  • lucene/memory/src/test/org/apache/lucene/index/memory/TestMemoryIndexAgainstDirectory.java:193-196, 200-207, 210-250, 259-268, 714-800; TestMemoryIndex.java:1093
  • lucene/CHANGES.txt:1476-1477 (GITHUB#15334), 5804-5805 (LUCENE-9314)
  • gh issue view 9801 / 8730 --repo apache/lucene (labels type:bug; read-only, 2026-10-27)
Suggested fix
  • lucene/core/src/java/org/apache/lucene/index/FreqProxTermsWriterPerField.java:147-152, 193-194, 208-219; lucene/core/src/java/org/apache/lucene/index/IndexingChain.java:2095-2097; lucene/highlighter/src/java/org/apache/lucene/search/uhighlight/MemoryIndexOffsetStrategy.java:112; lucene/highlighter/src/java/org/apache/lucene/search/highlight/WeightedSpanTermExtractor.java:411-413; lucene/CHANGES.txt:1476-1477 (GITHUB#15334)
History
  • a Lucene clone origin/branch_10_6 2763db11b0a lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java (storeTerms)

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 a probe run on main a9779ef3c193 (assertions on and off, 10.5.2, and with the fix shadowed); the values on screen come from the probe. The probe adds a DOCS_AND_FREQS field with frequencies a=7, b=1, the same case as the repro. Revised on 2026-10-27 after the diagram check: the code panel now declares Document doc (the panel's lines were compiled and run on main, same values), and some labels were reworded; no value changed.

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.