All findings · Lucene finding · LFV-574 · mlt-interesting-terms-presize-oome

MoreLikeThis.retrieveInterestingTerms sizes its result list by maxQueryTerms, so Integer.MAX_VALUE throws OutOfMemoryError even for a one-word document

retrieveInterestingTerms throws OutOfMemoryError for a one-word document when maxQueryTerms is Integer.MAX_VALUE ("no limit"), because it reserves that many list slots before it reads any word; like() with the same setting works. Smaller large limits cost about 4 bytes per unit per call (compressed object pointers on): 400 MB at 100,000,000.

Summary

Priority
Low, rank 651A huge maxQueryTerms such as Integer.MAX_VALUE makes retrieveInterestingTerms throw OutOfMemoryError while like() still works and no Lucene code calls it, a MAX_VALUE-as-limit loud row like LFV-438, so it follows the highlighter rare-value rows.
Impact
crash
Since
1.9 (2006): the line came with the method in 2005introduced 2005-05-10 · over 21 years
Affected
every release that ships MoreLikeThis, 1.9 through 10.6.0, and main (source scan of all 147 release tags; 22 releases run)
Status
documented
Found by
a hand-added boundary case in a reference check of MoreLikeThis's term selection

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

MoreLikeThis scores the words of a document and keeps the best maxQueryTerms of them (default 25). like() turns them into a query; retrieveInterestingTerms() returns them as a String[].

Summary of the bug. The user indexes one document, lucene, calls setMaxQueryTerms(Integer.MAX_VALUE) and retrieveInterestingTerms(new StringReader("lucene"), "text"); expected [lucene], as like() with the same setting returns one clause. Actual: line 914 of MoreLikeThis first creates new ArrayList<>(maxQueryTerms), an array of 2,147,483,647 empty slots where 1 is needed, and throws java.lang.OutOfMemoryError: Requested array size exceeds VM limit. Smaller limits work at about 4 bytes per unit: 400 MB per call at 100,000,000; 1,000,000,000 exhausts a 2 GB heap. Why: the term queue is already capped at Math.min(maxQueryTerms, number of words) and the loop stops at lim-- > 0, so the limit is a cap. Fix sketch: create the list after the queue, sized by what it can hold, in both overloads (lines 914 and 889).
A one-word document asked for its interesting words with maxQueryTerms = Integer.MAX_VALUE: line 914 first asks for an array of 2,147,483,647 slots to hold 1 word and throws; smaller limits work at about 4 bytes per unit (compressed object pointers on); the queue is already capped, so the fix sizes the list by the queue. Select the still to open it at full size. Generated with AI, checked against the code.
  1. Index one document whose text field is lucene. Ask MoreLikeThis for its interesting words with no practical limit: setMaxQueryTerms(Integer.MAX_VALUE), then retrieveInterestingTerms(new StringReader("lucene"), "text").
  2. You expect [lucene], the one word, just as like() with the same setting returns the one-clause query text:lucene.
  3. You get java.lang.OutOfMemoryError: Requested array size exceeds VM limit, on the tested OpenJDK 25 VM with a 2 GB or a 16 GB heap alike. The docNum overload fails the same way.
  4. Why: both overloads start with new ArrayList<>(maxQueryTerms) before they read the document, so Java is asked for an array of 2,147,483,647 slots to hold 1 word. like() sizes its queue by the words it finds: Math.min(maxQueryTerms, number of words).
  5. Below that array limit, memory follows the limit, not the words: 4 MB per call at 1,000,000, 400 MB at 100,000,000, and OutOfMemoryError: Java heap space at 1,000,000,000 with a 2 GB heap. like() allocates about 6 KB each time.

Who is affected

Applications that call either retrieveInterestingTerms overload after setting a huge maxQueryTerms, most naturally Integer.MAX_VALUE as "no limit". The default 25 is safe, and no Lucene code calls the method. The failure is an Error, so catch (Exception) misses it, and under -XX:+ExitOnOutOfMemoryError one call ends the JVM.

Mechanism, terms and numbers577 words

MoreLikeThis.retrieveInterestingTerms returns a document's most interesting words, at most maxQueryTerms of them. Both public overloads first run new ArrayList<>(maxQueryTerms) (MoreLikeThis.java:889 and :914 on main 645d4fe14a20) and only then build the term queue, so the list's array is sized by the limit, not by the words found. With setMaxQueryTerms(Integer.MAX_VALUE), read as "no limit", every call throws java.lang.OutOfMemoryError: Requested array size exceeds VM limit, even for a one-word document (on the tested OpenJDK 25 VM, with a 2 GB and a 16 GB heap alike); like() on the same object returns a one-clause query, because its queue is capped at Math.min(maxQueryTerms, number of words) (:627). Large values below the JVM's array limit return the right words but allocate about 4 bytes per unit of the limit on every call with compressed object pointers (400 MB at 100,000,000), or fail with "Java heap space". The line dates from 2005 (175cf8a9fd8) and is in every release that ships MoreLikeThis, 1.9 through 10.6.0, and main (a source scan of all 147 release tags; 22 releases and main fail when run).

The code (main 645d4fe14a20)

// MoreLikeThis.java:913-925; the docNum overload at :888-901 has the same body
public String[] retrieveInterestingTerms(Reader r, String fieldName) throws IOException {
  ArrayList<String> al = new ArrayList<>(maxQueryTerms);      // :914, before any word is known
  PriorityQueue<ScoreTerm> pq = retrieveTerms(r, fieldName);  // queue capped at min(maxQueryTerms, #words), :627
  ScoreTerm scoreTerm;
  ...
  int lim = maxQueryTerms;
  while (((scoreTerm = pq.pop()) != null) && lim-- > 0) {    // maxQueryTerms used as a cap
    al.add(scoreTerm.word);
  }
  ...
  1. setMaxQueryTerms only assigns the field (MoreLikeThis.java:528-530); the default is 25 (:232).
  2. new ArrayList<>(n) allocates new Object[n] at once for any positive n (JDK ArrayList.java:157). On the tested OpenJDK 25 HotSpot VM, the JVM refuses an array of Integer.MAX_VALUE elements outright ("Requested array size exceeds VM limit", the same with a 2 GB and a 16 GB heap); below that it allocates about 4 bytes per element with compressed object pointers enabled, as in every run here.
  3. The pre-size is never needed: createQueue builds the queue with final int limit = Math.min(maxQueryTerms, this.getTermsCount(perFieldTermFrequencies)); (:627) and adds words only while queue.size() < limit (:663), so the list never holds more than pq.size() words.
  4. like() (:555, :584-590), MoreLikeThisQuery.rewrite (MoreLikeThisQuery.java:71) and the XML query parser's <LikeThisQuery maxQueryTerms="..."> (LikeThisQueryBuilder.java:90) all go through createQueue and never allocate by the limit. No Lucene code calls retrieveInterestingTerms.

The numbers (main, assertions off, 2 GB heap, one-word document)

Bytes allocated by one call after a warm-up call, with compressed object pointers on. The fix column ran with the fixed class shadowed; no bytes were measured at the two largest limits.

maxQueryTermslike("text", reader)retrieveInterestingTerms(reader, "text")with the fix
25 (default)text:lucene, 6,176 bytes[lucene], 5,464 bytes[lucene], 5,368 bytes
1,000,000text:lucene, 6,176 bytes[lucene], 4.0 MB[lucene], 5,368 bytes
10,000,000text:lucene, 6,176 bytes[lucene], 40.0 MB[lucene], 5,368 bytes
100,000,000text:lucene, 6,176 bytes[lucene], 400.0 MB[lucene], 5,368 bytes
1,000,000,000text:luceneOutOfMemoryError: Java heap space[lucene]
Integer.MAX_VALUEtext:luceneOutOfMemoryError: Requested array size exceeds VM limit (also with a 16 GB heap)[lucene]

The docNum overload, retrieveInterestingTerms(0) on a second one-document index with the same text stored, throws the same error at Integer.MAX_VALUE from :889; with the fix it returns [lucene] at 25, 10,000 and Integer.MAX_VALUE.

Exposure and likelihood246 words
  • Who is hit. Code that calls retrieveInterestingTerms(int docNum) or retrieveInterestingTerms(Reader, String) with a maxQueryTerms far above the number of words it expects, for example Integer.MAX_VALUE to list every ranked keyword of a document. Before 4.10.0, 0 meant "no limit" for like(); since then 0 throws a NullPointerException (LFV-553), so a large value is today's way to ask for every word.
  • Default vs opt-in. The default (25) and ordinary values are safe. It takes a non-default, large limit plus the convenience method; any analyzer, codec, directory and document.
  • What goes wrong. At Integer.MAX_VALUE every call fails, on any document (the list is created before the document is read) and, on the tested VM, with a 2 GB and a 16 GB heap alike. The failure is a java.lang.Error; with -XX:+ExitOnOutOfMemoryError the JVM exited (code 3) although the caller caught Throwable. Without that flag nothing is left behind: the same MoreLikeThis and reader keep working. Below the array limit each call allocates about 4 bytes per unit of the limit with compressed object pointers (400 MB at 100,000,000) or fails with "Java heap space". No wrong results and no index change.
  • Scale. None needed: one word is enough; fixed input, one thread, assertions on or off.
  • Workaround. Pass a modest maxQueryTerms sized to the expected result; the default 25 is safe.
  • How likely. Rare: it fails loudly on the first call, so developers meet it in testing, and the documented searches found no public report.

Minimal repro

One JUnit method on the public API, in the queries.mlt package like the upstream tests: one document lucene, setMaxQueryTerms(Integer.MAX_VALUE), one call to retrieveInterestingTerms(Reader, String). Fixed input, one thread; it fails with assertions on and off and with every seed tried. With the fix it passes unchanged, so it is also the regression test.

TestMoreLikeThisInterestingTermsLargeMaxQueryTerms.java (lucene/queries, package org.apache.lucene.queries.mlt, 1 test)52 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.queries.mlt;

import java.io.StringReader;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.tests.util.LuceneTestCase;

public class TestMoreLikeThisInterestingTermsLargeMaxQueryTerms extends LuceneTestCase {

  public void testUnboundedMaxQueryTerms() throws Exception {
    try (StandardAnalyzer analyzer = new StandardAnalyzer();
        ByteBuffersDirectory dir = new ByteBuffersDirectory();
        IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(analyzer))) {
      Document doc = new Document();
      doc.add(new TextField("text", "lucene", Field.Store.NO));
      w.addDocument(doc);
      try (DirectoryReader reader = DirectoryReader.open(w)) {
        MoreLikeThis mlt = new MoreLikeThis(reader);
        mlt.setAnalyzer(analyzer);
        mlt.setMinTermFreq(1);
        mlt.setMinDocFreq(1);
        mlt.setMaxQueryTerms(Integer.MAX_VALUE);
        // expected [lucene]; actual OutOfMemoryError from new ArrayList<>(maxQueryTerms)
        assertArrayEquals(
            new String[] {"lucene"},
            mlt.retrieveInterestingTerms(new StringReader("lucene"), "text"));
      }
    }
  }
}
Command
cp $REPRO/TestMoreLikeThisInterestingTermsLargeMaxQueryTerms.java $LUCENE/lucene/queries/src/test/org/apache/lucene/queries/mlt/
cd $LUCENE && ./gradlew :lucene:queries:test --tests org.apache.lucene.queries.mlt.TestMoreLikeThisInterestingTermsLargeMaxQueryTerms -Ptests.seed=DEADBEEF
Expected
retrieveInterestingTerms returns [lucene].
Observed
java.lang.OutOfMemoryError: Requested array size exceeds VM limit from MoreLikeThis.retrieveInterestingTerms (MoreLikeThis.java:914).

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

More files, environment, full output255 words

Copy the test into lucene/queries/src/test/org/apache/lucene/queries/mlt/ of a Lucene checkout and run it with gradle (command below). It uses StandardAnalyzer, ByteBuffersDirectory and a plain IndexWriter, so the input is deterministic; LuceneTestCase still randomizes locale and time zone. On the tested OpenJDK 25 HotSpot VM the error is the JVM's array-length limit, not heap exhaustion (the same with a 2 GB and a 16 GB heap); other JVM implementations were not tried. The test covers the (Reader, String) overload only; the docNum overload's failure at :889 comes from the diagram probe and the verifiers' tests.

Executed: gradle on a clean 645d4fe14a20 (fails; done twice, in two fresh scratch worktrees); the standalone runner on the stock main jars with -ea and -da and on the 10.6.0 and 10.5.2 release jars (fails); seeds DEADBEEF and 1234ABCD.

Environment

lucenemain 645d4fe14a20 (11.0.0-SNAPSHOT): fails; also fails on 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 it ran on (on this HotSpot VM the error is the array-length limit, the same with a 2 GB and a 16 GB heap)
Command (environment)
cp $REPRO/TestMoreLikeThisInterestingTermsLargeMaxQueryTerms.java $LUCENE/lucene/queries/src/test/org/apache/lucene/queries/mlt/
cd $LUCENE && ./gradlew :lucene:queries:test --tests org.apache.lucene.queries.mlt.TestMoreLikeThisInterestingTermsLargeMaxQueryTerms -Ptests.seed=DEADBEEF

Observed output

# gradle, main 645d4fe14a20, -Ptests.seed=DEADBEEF
TestMoreLikeThisInterestingTermsLargeMaxQueryTerms > testUnboundedMaxQueryTerms FAILED
    java.lang.OutOfMemoryError: Requested array size exceeds VM limit
        at __randomizedtesting.SeedInfo.seed([DEADBEEF:CEA0F8138A2E1EB2]:0)
        at java.base/java.util.ArrayList.<init>(ArrayList.java:157)
        at org.apache.lucene.queries.mlt.MoreLikeThis.retrieveInterestingTerms(MoreLikeThis.java:914)
        at org.apache.lucene.queries.mlt.TestMoreLikeThisInterestingTermsLargeMaxQueryTerms.testUnboundedMaxQueryTerms(TestMoreLikeThisInterestingTermsLargeMaxQueryTerms.java:48)

# standalone runner, seed DEADBEEF
main 645d4fe14a20   -ea / -da   tests_run=1 failures=1   same OutOfMemoryError at MoreLikeThis.java:914
10.6.0 (release)    -ea / -da   tests_run=1 failures=1   same
10.5.2 (release)    -ea         tests_run=1 failures=1   same

Why it looks unintended

  • setMaxQueryTerms sets a maximum with no documented bound or check; the method promises "the most interesting words in the document".
  • like() on the same object accepts the same value, and the method's own loop uses the limit only as a cap.
  • Lucene fixed this pattern as a bug in LUCENE-2119, and its PriorityQueue rejects huge sizes "to prevent confusing OOME".
The full argument335 words
  • Contract. setMaxQueryTerms and getMaxQueryTerms document "the maximum number of query terms that will be included in any generated query" (MoreLikeThis.java:512-530), with no bound and no validation. retrieveInterestingTerms(Reader, String) documents "@return the most interesting words in the document" and "@see #setMaxQueryTerms" (:903-912). Nothing mentions an allocation proportional to the limit. The expectation is implied, not spelled out.
  • Same object, same setting. like() handles any value because its queue is sized by the words found (:627); only the convenience method fails.
  • The clause limit is no bound either. The default's javadoc points at IndexSearcher#getMaxClauseCount (:228), but IndexSearcher.setMaxClauseCount accepts any value >= 1, Integer.MAX_VALUE included (IndexSearcher.java:256-261).
  • "No limit" used to be 0. Up to 4.9.x createQuery stopped only if (maxQueryTerms > 0 && qterms >= maxQueryTerms), so 0 meant no limit; LUCENE-5795 (4.10.0) removed that, and 0 now throws a NullPointerException (LFV-553).
  • Precedent. LUCENE-2119, under Bug fixes in the "Release 2.9.3 / 3.0.2" section: "Don't throw NegativeArraySizeException if you pass Integer.MAX_VALUE as nDocs to IndexSearcher search methods" (CHANGES.txt:18439-18441). Its issue text (Michael McCandless): "This trips users up fairly often because they assume our PQ doesn't statically pre-allocate (a reasonable assumption...)"; the fix capped the size at maxDoc, still in place (IndexSearcher.java:588, :597, :721, :729). LUCENE-8221 also fixed a large-value overflow in this class as a bug (CHANGES.txt:7456, 7.4.0).
  • Lucene avoids this failure on purpose elsewhere. The PriorityQueue that createQueue builds rejects a size >= ArrayUtil.MAX_ARRAY_LENGTH with an IllegalArgumentException and the comment "Throw exception to prevent confusing OOME" (PriorityQueue.java:121-124). An OutOfMemoryError from a size parameter is not a failure Lucene accepts.
  • History. The pre-size came with the method (175cf8a9fd8, 2005, "Added convenience routine retrieveInterestingTerms()"), when it still returned every word; later 81087e8bb66 made it return only the top terms and kept the pre-size. No test calls retrieveInterestingTerms, and the documented searches found no commit, issue or list thread that discusses the pre-size.
The devil's-advocate check167 words

The strongest "works as designed" case (the intended-behaviour verifier and the cross-model reviewer): the default is 25 and its javadoc points at the clause limit (1024 by default); configuration values are "trusted as given" (SECURITY.md:42); pre-sizing a list to its limit is a common Java idiom; the LUCENE-2119 issue text itself calls Integer.MAX_VALUE "a nonsense value to pass in"; and createQueue's min() came from LUCENE-5795 to make term selection cheaper, not as a guard against large values.

Why it fails: no bound is documented or checked, and the clause limit itself accepts Integer.MAX_VALUE; like() on the same object accepts the value; the method's own loop treats it as a cap; Lucene's own PriorityQueue guards against exactly this "confusing OOME"; LUCENE-2119 called the user's assumption reasonable and fixed the caller. "Trusted configuration" defines security scope, not which accepted settings may fail. The argument bears on priority, not on correctness. The fix is a small robustness change; the createQueue min() is not a deliberate guard.

Suggested fix

Recommended Size the list after the queue exists

Create the list after the term queue and size it by what the queue holds: new ArrayList<>(Math.min(maxQueryTerms, pq.size())), in both overloads (2 lines changed). The minimal test and the whole queries suite pass; with the fix, (Reader, String) returns [lucene] at every probed limit up to Integer.MAX_VALUE, 5,368 bytes per call where measured (25 to 100,000,000).

public String[] retrieveInterestingTerms(Reader r, String fieldName) throws IOException {
  PriorityQueue<ScoreTerm> pq = retrieveTerms(r, fieldName);
  ArrayList<String> al = new ArrayList<>(Math.min(maxQueryTerms, pq.size()));
  // was, before retrieveTerms: ArrayList<String> al = new ArrayList<>(maxQueryTerms);
  ...
// the same change in retrieveInterestingTerms(int docNum), after pq = retrieveTerms(docNum)
All options and trade-offs3 options · 459 words

Three options. Option A was validated with gradle in a scratch worktree at 645d4fe14a20 and with the standalone runner; B and C were not executed. Options A and B do not change LFV-553 (maxQueryTerms = 0 throws a NullPointerException in createQueue); only option C's separate <= 0 check would.

Size the list after the queue exists (validated by the checks) (recommended)

In retrieveInterestingTerms(int docNum) and retrieveInterestingTerms(Reader, String), move the list creation below retrieveTerms(...) and size it by Math.min(maxQueryTerms, pq.size()) (one line removed and one added in each overload).

public String[] retrieveInterestingTerms(Reader r, String fieldName) throws IOException {
  PriorityQueue<ScoreTerm> pq = retrieveTerms(r, fieldName);
  ArrayList<String> al = new ArrayList<>(Math.min(maxQueryTerms, pq.size()));
  // was, before retrieveTerms: ArrayList<String> al = new ArrayList<>(maxQueryTerms);
  ...
// the same change in retrieveInterestingTerms(int docNum), after pq = retrieveTerms(docNum)

Trade-offs

  • No change in results: the same loop fills the list, and pq.size() never exceeds maxQueryTerms (MoreLikeThis.java:627, :663), so the min is only defensive.
  • A negative maxQueryTerms still throws IllegalArgumentException, now from the queue in createQueue (PriorityQueue rejects a negative size) instead of from ArrayList ("Illegal Capacity"): a visible but obscure change for a caller that inspects the message or the stack. 0 still throws LFV-553's NullPointerException.

What the checks validated

  • Gradle, clean scratch worktree at 645d4fe14a20, JDK 25, -Ptests.seed=DEADBEEF: without the fix the minimal test fails; with it, :lucene:queries:checkGoogleJavaFormat :lucene:queries:test passes: 52 suites, 513 tests, 0 failures, 5 skipped, including the minimal test 1/1 and TestMoreLikeThis 11 tests (1 skipped).
  • Repeated in a second fresh scratch worktree (./gradlew --no-daemon), with the same results.
  • Standalone runner with the fixed class shadowed: the minimal test plus TestMoreLikeThis, 11 run, 0 failed, with -ea and -da (unfixed: 1 failure).
  • Allocation probe with the fixed class shadowed: (Reader, String) returns [lucene] at every probed limit up to Integer.MAX_VALUE and allocates 5,368 bytes at each measured limit (25 to 100,000,000); the docNum overload returns [lucene] at 25, 10,000 and Integer.MAX_VALUE. Apart from the minimal test (at Integer.MAX_VALUE), the gradle run does not exercise large limits.
  • Earlier, the finder's, the independent reproducer's and the adjudication's tests passed with the same fix.

Size by the queue alone, or do not pre-size

new ArrayList<>(pq.size()), or new ArrayList<>(), after retrieveTerms(...).

Trade-offs

Equivalent to the recommended option for every value, since the queue is already capped at maxQueryTerms; slightly simpler. Not executed.

Validate in setMaxQueryTerms

Reject values above some bound (and, separately, <= 0).

Trade-offs

An upper bound is arbitrary, turns a setting that like() handles today into an error, and below the bound still leaves an allocation proportional to the limit. Rejecting <= 0 would cover LFV-553, not this bug. Not recommended on its own; not executed.

Downstream note

No Lucene path feeds query input to this method (the XML query parser's maxQueryTerms reaches only like(), which is unaffected), so an application that forwards a client-chosen maxQueryTerms to retrieveInterestingTerms has to bound it itself.

How it was found

A bounded-random check compared MoreLikeThis's real term selection with a reference written from its javadoc, under the rule "no exception for any value the setters accept". Its random limits stayed between 0 and 12, so the finder added large limits by hand; Integer.MAX_VALUE failed at once. Not a blind find: the boundary was chosen.

The method in full129 words

Method: semi-formal translation validation (no model checker), in a hunt round on MoreLikeThis and CommonTermsQuery against reference semantics.

  • A harness ran the real queries jar against a reference term-selection semantics written from the javadoc, with bounded-random documents and settings, and checked several rules; rule R6 was "no exception for any parameter value the setters accept".
  • The randomized runs drew maxQueryTerms from 0 to 12, so they could not reach large values. The finder added a boundary probe by hand with Integer.MAX_VALUE, 1,000,000,000 and 100,000,000: the first threw OutOfMemoryError at once, the second ran out of a 1 GB heap, the third returned after allocating about 400 MB.
  • Code reading located the pre-size; the finding was reduced to a public-API JUnit test, later to the single test shown here.

Prior reports, history and checks

Prior reports

No prior report was found in the documented searches (2026-10-26, re-checked 2026-10-30) of GitHub issues, PRs and commits including the JIRA archive, the mailing lists, downstream trackers or the web. Related only: LUCENE-5795 added the queue cap that makes like() safe; Elasticsearch deleted the method from its fork as dead code.

Searches6 searches

Nearest items, none the same bug: #6857 (LUCENE-5795, the createQueue cap; its comments never mention this method), #2372 (LUCENE-1295) and #4399 (LUCENE-3326), which added the two overloads, #9268 (LUCENE-8221, a different large-value overflow in this class, fixed as a bug), #9373 (LUCENE-8326, an open refactor whose patches keep the pre-size). Open PRs #940 and #16847 touch the file but not this code. Downstream: Elasticsearch removed the method from its fork as dead code (elastic/elasticsearch#87825, 2022, no bug named); the OpenSearch fork and the Lucene.NET port keep the same line with no caller and no report found. Coverage limits: keyword search only, at most 50 GitHub results per query, private reports not searchable. No maintainer has commented on this bug.

What was searched, when, and what turned up
WhereWhenHowFound
Search tool (GitHub issues/PRs/commits incl. the JIRA archive, dev@, java-user@, issues@, commits@, downstream, web)2026-10-26scripted prior-report searches, 290 queries: method and class names, the error texts, maxQueryTerms with large values, the pre-size patternno hit about this code
JIRA, mailing lists, GitHub (apache/lucene, lucene-solr, solr, Elasticsearch, OpenSearch, Lucene.NET, all of GitHub)2026-10-2610 JQL queries, 40 lists.apache.org queries, 25 GraphQL searches, 4 code searchesLUCENE-5795, LUCENE-1295, LUCENE-3326, LUCENE-8221, LUCENE-8326, elastic/elasticsearch#87825: related, none the same bug
Open PRs2026-10-26changed files of all 406 open apache/lucene PRs#940 and #16847 touch MoreLikeThis.java, not this code
GitHub, re-check2026-10-30issue/PR search for retrieveInterestingTerms, MoreLikeThis OutOfMemoryError, maxQueryTerms Integer.MAX_VALUE, MoreLikeThis maxQueryTerms ArrayList, interesting terms MoreLikeThis; newest commits on MoreLikeThis.java; the tracking issueonly #2372 and #4399 (unrelated); the file unchanged on upstream main 208608f75307 (then 2f424fd428da); no comment on this bug
GitHub, second re-check2026-10-30issue/PR search for retrieveInterestingTerms, MoreLikeThis OutOfMemoryError, maxQueryTerms Integer.MAX_VALUE, MoreLikeThis maxQueryTerms; open PRs matching MoreLikeThis; newest commits on MoreLikeThis.java; the tracking issueonly #2372 and #4399 (unrelated); open PR #940 (not this code); the file unchanged on upstream main 2f424fd428da; no comment on this bug
GitHub, third re-check2026-10-30read-only gh api and gh search: upstream main, newest commits on MoreLikeThis.java, issue #16937 and its comments, the four queries of the second re-check, open PRs matching MoreLikeThisupstream main 2f424fd428da; newest MoreLikeThis.java commit 448c7d8e2414; #16937 has 3 comments, none mentioning MoreLikeThis, retrieveInterestingTerms or maxQueryTerms; only #2372 and #4399; open PR #940 (not this code)

History

Introduced on 2005-05-10 in 175cf8a9fd8, the commit that added retrieveInterestingTerms as a "convenience routine"; later overloads copied the body. First released in 1.9 (2006); every release from 1.9 to 10.6.0 and main is affected (a source scan of all 147 release tags; 22 releases run), and nothing ever fixed it.

Versions tested6 rows

The line arrived with the method in 175cf8a9fd8 (svn r169508, 2005-05-10, David Spencer, contrib/similarity, no issue); the same-day 81087e8bb66 added the top-terms cap and kept it. LUCENE-1295 (1979d756055, 2008, 2.4.0) and LUCENE-3326 (bfea7d6a155 / 07bfe50eda6, 2011, 3.4.0 / 4.0.0) copied the body into the docNum and (Reader, String) overloads; later edits only retyped or moved it (LUCENE-1257 generics, LUCENE-5512 diamond, f9f6d3a3ac4 in 2017). LUCENE-5795 (1427d574d3d / 173a44e67c7, 2014, 4.10.0) capped the queue in createQueue, which made like() safe, and left the pre-size. A scan of all 147 release tags that contain the file finds the pre-size in every one. Old releases ran on JDK 25; 10.6.0 ran from its checksum-verified release jars (the history clone has no 10.6 tag).

Versions and branches executed, with results
Version or commitVerdictResult
main 645d4fe14a20 affectedthe minimal test fails (gradle; standalone -ea and -da)
10.6.0, 10.5.2 (release jars) affectedthe minimal test fails with the same OutOfMemoryError
1.9.1 to 10.5.2 21 releasesfail on 1.9.1, 2.0.0, 2.3.2, 2.4.0, 2.9.4, 3.0.3, 3.6.2, 4.0.0, 4.9.1 (the finder's run), 4.10.4, 5.0.0, 5.5.5, 6.6.6, 7.7.3, 8.9.0, 8.10.1, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2
175cf8a9fd8 and 81087e8bb66 (2005, on 1.9.1 core) introducing commitsboth fail; 81087e8bb66 with only the pre-size removed passes
before 175cf8a9fd8; releases up to 1.4.3 not affectedno retrieveInterestingTerms (no MoreLikeThis in any release before 1.9)
fix (scratch worktree at 645d4fe14a20) not affectedthe minimal test and the whole queries suite pass (513 tests, 0 failures), in two fresh scratch worktrees

Independent checks

An independent reproducer with its own tests, an intended-behaviour devil's advocate, a reading-only review by a model from another family, an adjudication, a registry re-run and a credibility audit all confirm it; this doc re-ran the one test on main and 10.6.0 and validated the fix with gradle.

Checks and verdicts322 words

Every cross-model review ran reading only (or stopped before a verdict) and could not run Java; the refute lens therefore returned a procedural "not real" while agreeing on the root cause and that the inputs are in contract. No executing cross-model review exists.

Each independent check and its verdict
CheckVerdictWhat it found
finder (reference check, boundary probe) candidateJUnit fails on main (-ea, -da), 10.5.2, 9.12.3, 4.10.4 and 4.9.1; with the fix the repro and TestMoreLikeThis pass (11 run, 0 failed).
independent reproducer real, lowOwn 5-test JUnit (both overloads, 1,000,000,000 under a 2 GB heap, controls) fails 3 of 5 on main (-ea, -da), 10.5.2 and 9.12.3; controls pass; with the fix 5 of 5.
cross-model review, refute lens (OpenAI GPT-5.6, reading only) procedural "not real" (low)Could not run Java; agreed on the root cause and that the inputs are in contract; found no substantive refutation. A separate run's own docNum test fails 1 of 3 under -ea and -da; controls 25 and 10,000 pass.
intended-behaviour devil's advocate realBuilt the works-as-designed case (default 25, clause-limit link, trusted configuration, pre-size idiom) and refuted it; its docNum test fails, control 1024 passes.
adjudication confirmed (high)Re-ran the independent reproducer's test with new seeds, -ea and -da: 3 of 5 fail each, controls pass; with the fix 5 of 5. Security none.
registry re-run and credibility audit keepThe finder's JUnit fails 1 of 1 under -ea and -da with fresh seeds; a code re-read confirms the needless pre-size against the createQueue cap.
this doc reproduced; fix validatedThe minimal test with gradle on a clean 645d4fe14a20 (fails) and with the fix (the whole queries suite passes, format check clean), done twice in two fresh scratch worktrees; standalone on main (-ea, -da) and 10.6.0 (fails); the fixed class shadowed with TestMoreLikeThis (passes).

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

Finding record

LFV-574, snapshot of 2026-11-06

Registry title
MoreLikeThis.retrieveInterestingTerms pre-sizes its result list to maxQueryTerms, so a large maxQueryTerms that like() accepts (for example Integer.MAX_VALUE as 'no limit') throws OutOfMemoryError even for a one-word document
Area
queries / MoreLikeThis interesting-terms convenience API (MoreLikeThis.retrieveInterestingTerms(int) and retrieveInterestingTerms(Reader, String))
Priority
Low, rank 651: A huge maxQueryTerms such as Integer.MAX_VALUE makes retrieveInterestingTerms throw OutOfMemoryError while like() still works and no Lucene code calls it, a MAX_VALUE-as-limit loud row like LFV-438, so it follows the highlighter rare-value rows.
Impact class
crash
Impact note
Applications on any release that ships MoreLikeThis (1.9 through 10.6.0) or on main that set maxQueryTerms to a huge value and call either public retrieveInterestingTerms overload get java.lang.OutOfMemoryError instead of the document's few interesting words, with -ea and -da and the default codec, directory and searcher: Integer.MAX_VALUE always fails ('Requested array size exceeds VM limit') whatever the heap size and without consuming heap, 1,000,000,000 fails with 'Java heap space' under -Xmx2g, and values in the tens to hundreds of millions succeed but allocate about 4 bytes per unit of maxQueryTerms on every call (400 MB at 100,000,000 for a one-word text); like() with the same setting works; the Error escapes catch (Exception) handlers and ends the JVM under -XX:+ExitOnOutOfMemoryError (executed); no code in Lucene calls the method, the XML query parser's maxQueryTerms attribute reaches only the unaffected like(), and there are no wrong results, no corruption and no lasting state.
Summary
You set MoreLikeThis.setMaxQueryTerms(Integer.MAX_VALUE), meaning 'no limit', and call retrieveInterestingTerms(...) on a one-word document. You expect that one word back, just as like() with the same setting returns a one-clause query. Instead both overloads begin with 'new ArrayList<>(maxQueryTerms)' (MoreLikeThis.java:889 and :914), sized from the limit rather than from the number of terms found, and throw java.lang.OutOfMemoryError: Requested array size exceeds VM limit.
Introduced
175cf8a9fd876aa7baaf7da1a2273e6ea984e8b1 (svn r169508, lucene/java/trunk, author dspencer, 2005-05-10: '[2] Added convenience routine retrieveInterestingTerms()', contrib/similarity; no issue and no pull request) introduced 'ArrayList al = new ArrayList( maxQueryTerms);' [read: git history clone; exec: on 1.9.1 core the file at 175cf8a9fd8 and at the same-day follow-up 81087e8bb66 (which added the 'lim-- > 0' result cap) both fail, the file just before (a79c508580a) has no retrieveInterestingTerms, and 81087e8bb66 with only the pre-size removed passes]. The same body, pre-size included, was copied into retrieveInterestingTerms(int docNum) by LUCENE-1295 (1979d756055, 2008, first released in 2.4.0) and into retrieveInterestingTerms(Reader, String) by LUCENE-3326 (bfea7d6a155 on 3.x / 07bfe50eda6 on trunk, 2011, 3.4.0 / 4.0.0); later edits kept it: LUCENE-1257 generics (5b83cc59b2e), LUCENE-5512 diamond (3b67b174932), LUCENE-5795 (1427d574d3d / 173a44e67c7, 2014, which capped createQueue at min(maxQueryTerms, #terms) and so made like() safe) and f9f6d3a3ac4 (2017, ArrayList<String>, current blame of both lines) (2005-05-10)
First affected version
1.9 (first release tag containing the line, 2006-02-27; already in 1.9-rc1, 2006-02-21) [read: release-tag scan]; the earliest release on Maven Central, 1.9.1 (identical MoreLikeThis.java), fails [exec: lucene-queries and lucene-similarity 1.9.1 jars]; no earlier release ships MoreLikeThis (1.4.3 and older [read]), so there is no last unaffected release
Affected versions
every release that ships MoreLikeThis, 1.9 through 10.6.0, and main ea57360912ed; never fixed [exec, -ea, Integer.MAX_VALUE: 1.9.1, 2.0.0, 2.3.2, 2.4.0, 2.9.4, 3.0.3, 3.6.2, 4.0.0, 4.9.1, 4.10.4, 5.0.0, 5.5.5, 6.6.6, 7.7.3, 8.9.0, 8.10.1, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2, 10.6.0 (release jars, in the bug doc) and main fail; -da: main fails (finder, the independent reproduction, the cross-model review, the intended-behaviour check, adjudication, registry re-run); the independent reproduction ran both overloads on main, 10.5.2 and 9.12.3] [read: all 147 release tags in the history clone that contain MoreLikeThis.java have the pre-size in every retrieveInterestingTerms body; 8.8.2 to 8.11.4 are not tagged in the clone, and 8.9.0, 8.10.1 and 8.11.4 were executed]; retrieveInterestingTerms(int docNum) is affected from 2.4.0 and retrieveInterestingTerms(Reader, String) from 3.4.0, and the deprecated retrieveInterestingTerms(Reader) (1.9 to 3.6.x) has the pre-size or delegates to the (Reader, String) overload; upstream main a95c754ee003 (2026-10-26) has a byte-identical MoreLikeThis.java and branch_10x the same lines [read]
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
Upstream and prior reports
none found (2026-10-26, read-only, the prior-report record verdict new: two scripted prior-report searches in the follow-up plus the finder's run (290 queries: GitHub issues and PRs, commits, downstream repositories, dev@, java-user@, issues@ and commits@ archives, the JIRA archive and the web), 10 manual JIRA queries over LUCENE and SOLR, 40 lists.apache.org queries, 25 GitHub GraphQL searches (apache/lucene, lucene-solr, solr, Elasticsearch, OpenSearch, Lucene.NET, global), 4 GitHub code searches, the changed-file lists of all 406 open apache/lucene PRs, upstream main and branch_10x contents, CHANGES.txt and the local history; registration check re-check: gh search 'retrieveInterestingTerms' finds only #2372 and #4399, 'MoreLikeThis OutOfMemoryError', 'maxQueryTerms Integer.MAX_VALUE' and 'MoreLikeThis ArrayList maxQueryTerms' find nothing). No hit describes this bug. Nearest: #6857 / LUCENE-5795 (added the createQueue cap that makes like() safe; its 10 comments never mention retrieveInterestingTerms), #2372 / LUCENE-1295 and #4399 / LUCENE-3326 (added the two overloads; unrelated discussion), #9268 / LUCENE-8221 (a different MoreLikeThis large-value overflow, fixed as a bug), #9373 / LUCENE-8326 (open refactor whose unmerged patches keep the pre-size); open PRs #940 and #16847 touch MoreLikeThis.java but not this code. Downstream: Elasticsearch deleted the method from its fork as dead code in elastic/elasticsearch#87825 (2022, no bug named); the OpenSearch fork and the Lucene.NET port keep the same pre-size with no caller found; Elasticsearch #48007, #49927 and #49966 concern maxQueryTerms = 0 (the separate LFV-553). Not fixed upstream (main a95c754ee003 [read])
Lucene commit tested
main 645d4fe14a20 (11.0.0-SNAPSHOT): the minimal test with gradle in a clean scratch worktree (fails) and with the fix (passes; the whole queries test suite, 52 suites, 513 tests, 0 failures, 5 skipped, and the format check), done twice on 2026-10-30 in two fresh worktrees with the same result; the standalone runner with -ea and -da, and with the fixed class shadowed together with the upstream TestMoreLikeThis (passes); an allocation probe without and with the fix; the 10.6.0 and 10.5.2 release jars (the minimal test fails). The hunt ran on main ea57360912ed, where MoreLikeThis.java is identical; the history sweep executed 26 runs from the introducing commit's parent to main (21 release runs covering 20 releases from 1.9.1 to 10.5.2, 1.9.1 twice; 4 commit-level runs; main), and the finder ran 4.9.1. MoreLikeThis.java is unchanged on upstream main 2f424fd428da (2026-10-30): its newest commit is still 448c7d8e2414 (#15681, 2026-02-11).

Lucene and upstream sources

Finding record
  • lucene/queries/src/java/org/apache/lucene/queries/mlt/MoreLikeThis.java:225-232 (at ea57360912ed) (DEFAULT_MAX_QUERY_TERMS = 25, 'Return a Query with no more than this many terms', @see IndexSearcher#getMaxClauseCount), :513-530 (getMaxQueryTerms / setMaxQueryTerms javadoc 'the maximum number of query terms that will be included in any generated query'; the setter only assigns), :624-629 (createQueue: limit = Math.min(maxQueryTerms, getTermsCount(...)) at :627), :888-899 (retrieveInterestingTerms(int): 'new ArrayList<>(maxQueryTerms)' at :889, 'int lim = maxQueryTerms', 'lim-- > 0'), :901-925 (retrieveInterestingTerms(Reader, String) javadoc '@return the most interesting words in the document', '@see #setMaxQueryTerms'; pre-size at :914)
  • lucene/queries/src/java/org/apache/lucene/queries/mlt/MoreLikeThisQuery.java:60-79 (at ea57360912ed) (rewrite passes maxQueryTerms to a new MoreLikeThis and calls like(), not retrieveInterestingTerms)
  • lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/builders/LikeThisQueryBuilder.java:89-90 (at ea57360912ed) (maxQueryTerms attribute read from the XML query); lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/CorePlusQueriesParser.java:51 (registers LikeThisQuery)
  • lucene/CHANGES.txt: (at ea57360912ed) LUCENE-2119 under Bug fixes ('Don't throw NegativeArraySizeException if you pass Integer.MAX_VALUE as nDocs to IndexSearcher search methods'), LUCENE-8221 (setMaxDocFreqPct overflow) (both cited by the intended-behaviour check)
The finding in 30 seconds
  • a Lucene main checkout lucene/queries/src/java/org/apache/lucene/queries/mlt/MoreLikeThis.java:225-232, 265, 512-530, 555, 584-590, 624-629, 663-665, 888-925 @ 645d4fe14a20
  • a Lucene main checkout lucene/queries/src/java/org/apache/lucene/queries/mlt/MoreLikeThisQuery.java:60-79; lucene/queryparser/src/java/org/apache/lucene/queryparser/xml/builders/LikeThisQueryBuilder.java:90 @ 645d4fe14a20
  • JDK 25.0.3 lib/src.zip java.base/java/util/ArrayList.java:155-157
Why it looks unintended
  • a Lucene main checkout lucene/queries/src/java/org/apache/lucene/queries/mlt/MoreLikeThis.java:225-232, 512-530, 627, 888-925 @ 645d4fe14a20
  • a Lucene main checkout lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java:80, 256-261, 588, 597, 721, 729 @ 645d4fe14a20
  • a Lucene main checkout lucene/CHANGES.txt:18439-18441 (LUCENE-2119, Release 2.9.3 / 3.0.2, Bug fixes; section header :18412), :7456 (LUCENE-8221, Lucene 7.4.0), :11949 (LUCENE-5795, 4.10.0)
  • a Lucene main checkout lucene/core/src/java/org/apache/lucene/util/PriorityQueue.java:115-125 @ 645d4fe14a20 ('Throw exception to prevent confusing OOME'); lucene/core/src/java/org/apache/lucene/util/ArrayUtil.java:32-34 (MAX_ARRAY_LENGTH)
Suggested fix
  • a Lucene main checkout lucene/core/src/java/org/apache/lucene/util/PriorityQueue.java:117-125 @ 645d4fe14a20 (negative size rejected)
  • JDK 25.0.3 lib/src.zip java.base/java/util/ArrayList.java:155-163 (negative capacity: IllegalArgumentException "Illegal Capacity")

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, the minimal repro (one document, lucene) and an executed probe on the stock main jars (assertions on and off), the 10.5.2 jar and with the fix; every code line and number on screen is checked against those sources by the diagram's own checker.

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.