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[].
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.- Index one document whose
textfield islucene. AskMoreLikeThisfor its interesting words with no practical limit:setMaxQueryTerms(Integer.MAX_VALUE), thenretrieveInterestingTerms(new StringReader("lucene"), "text"). - You expect
[lucene], the one word, just aslike()with the same setting returns the one-clause querytext:lucene. - 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. ThedocNumoverload fails the same way. - 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). - 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 spaceat 1,000,000,000 with a 2 GB heap.like()allocates about 6 KB each time.
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).
// 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);
}
...setMaxQueryTermsonly assigns the field (MoreLikeThis.java:528-530); the default is 25 (:232).new ArrayList<>(n)allocatesnew Object[n]at once for any positiven(JDKArrayList.java:157). On the tested OpenJDK 25 HotSpot VM, the JVM refuses an array ofInteger.MAX_VALUEelements 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.- The pre-size is never needed:
createQueuebuilds the queue withfinal int limit = Math.min(maxQueryTerms, this.getTermsCount(perFieldTermFrequencies));(:627) and adds words only whilequeue.size() < limit(:663), so the list never holds more thanpq.size()words. like()(:555,:584-590),MoreLikeThisQuery.rewrite(MoreLikeThisQuery.java:71) and the XML query parser's<LikeThisQuery maxQueryTerms="...">(LikeThisQueryBuilder.java:90) all go throughcreateQueueand never allocate by the limit. No Lucene code callsretrieveInterestingTerms.
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.
maxQueryTerms | like("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,000 | text:lucene, 6,176 bytes | [lucene], 4.0 MB | [lucene], 5,368 bytes |
| 10,000,000 | text:lucene, 6,176 bytes | [lucene], 40.0 MB | [lucene], 5,368 bytes |
| 100,000,000 | text:lucene, 6,176 bytes | [lucene], 400.0 MB | [lucene], 5,368 bytes |
| 1,000,000,000 | text:lucene | OutOfMemoryError: Java heap space | [lucene] |
Integer.MAX_VALUE | text:lucene | OutOfMemoryError: 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)orretrieveInterestingTerms(Reader, String)with amaxQueryTermsfar above the number of words it expects, for exampleInteger.MAX_VALUEto list every ranked keyword of a document. Before 4.10.0, 0 meant "no limit" forlike(); since then 0 throws aNullPointerException(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_VALUEevery 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 ajava.lang.Error; with-XX:+ExitOnOutOfMemoryErrorthe JVM exited (code 3) although the caller caughtThrowable. Without that flag nothing is left behind: the sameMoreLikeThisand 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
maxQueryTermssized 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.
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.
/*
* 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"));
}
}
}
}
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
retrieveInterestingTermsreturns[lucene].- Observed
java.lang.OutOfMemoryError: Requested array size exceeds VM limitfromMoreLikeThis.retrieveInterestingTerms(MoreLikeThis.java:914).
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.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT): fails; also fails on the 10.6.0 and 10.5.2 release jars |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux 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) |
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
# 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 samesetMaxQueryTermssets 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
PriorityQueuerejects huge sizes "to prevent confusing OOME".
The full argument335 words
- Contract.
setMaxQueryTermsandgetMaxQueryTermsdocument "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), butIndexSearcher.setMaxClauseCountaccepts any value>= 1,Integer.MAX_VALUEincluded (IndexSearcher.java:256-261). - "No limit" used to be 0. Up to 4.9.x
createQuerystopped onlyif (maxQueryTerms > 0 && qterms >= maxQueryTerms), so 0 meant no limit; LUCENE-5795 (4.10.0) removed that, and 0 now throws aNullPointerException(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 atmaxDoc, 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
PriorityQueuethatcreateQueuebuilds rejects a size>= ArrayUtil.MAX_ARRAY_LENGTHwith anIllegalArgumentExceptionand the comment "Throw exception to prevent confusing OOME" (PriorityQueue.java:121-124). AnOutOfMemoryErrorfrom 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; later81087e8bb66made it return only the top terms and kept the pre-size. No test callsretrieveInterestingTerms, 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.
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.
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 exceedsmaxQueryTerms(MoreLikeThis.java:627,:663), so theminis only defensive. - A negative
maxQueryTermsstill throwsIllegalArgumentException, now from the queue increateQueue(PriorityQueuerejects a negative size) instead of fromArrayList("Illegal Capacity"): a visible but obscure change for a caller that inspects the message or the stack. 0 still throws LFV-553'sNullPointerException.
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:testpasses: 52 suites, 513 tests, 0 failures, 5 skipped, including the minimal test 1/1 andTestMoreLikeThis11 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-eaand-da(unfixed: 1 failure). - Allocation probe with the fixed class shadowed:
(Reader, String)returns[lucene]at every probed limit up toInteger.MAX_VALUEand allocates 5,368 bytes at each measured limit (25 to 100,000,000); thedocNumoverload returns[lucene]at 25, 10,000 andInteger.MAX_VALUE. Apart from the minimal test (atInteger.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.
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.
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.
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.
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
maxQueryTermsfrom 0 to 12, so they could not reach large values. The finder added a boundary probe by hand withInteger.MAX_VALUE, 1,000,000,000 and 100,000,000: the first threwOutOfMemoryErrorat 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.
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.
| Where | When | How | Found |
|---|---|---|---|
| Search tool (GitHub issues/PRs/commits incl. the JIRA archive, dev@, java-user@, issues@, commits@, downstream, web) | 2026-10-26 | scripted prior-report searches, 290 queries: method and class names, the error texts, maxQueryTerms with large values, the pre-size pattern | no hit about this code |
| JIRA, mailing lists, GitHub (apache/lucene, lucene-solr, solr, Elasticsearch, OpenSearch, Lucene.NET, all of GitHub) | 2026-10-26 | 10 JQL queries, 40 lists.apache.org queries, 25 GraphQL searches, 4 code searches | LUCENE-5795, LUCENE-1295, LUCENE-3326, LUCENE-8221, LUCENE-8326, elastic/elasticsearch#87825: related, none the same bug |
| Open PRs | 2026-10-26 | changed files of all 406 open apache/lucene PRs | #940 and #16847 touch MoreLikeThis.java, not this code |
| GitHub, re-check | 2026-10-30 | issue/PR search for retrieveInterestingTerms, MoreLikeThis OutOfMemoryError, maxQueryTerms Integer.MAX_VALUE, MoreLikeThis maxQueryTerms ArrayList, interesting terms MoreLikeThis; newest commits on MoreLikeThis.java; the tracking issue | only #2372 and #4399 (unrelated); the file unchanged on upstream main 208608f75307 (then 2f424fd428da); no comment on this bug |
| GitHub, second re-check | 2026-10-30 | issue/PR search for retrieveInterestingTerms, MoreLikeThis OutOfMemoryError, maxQueryTerms Integer.MAX_VALUE, MoreLikeThis maxQueryTerms; open PRs matching MoreLikeThis; newest commits on MoreLikeThis.java; the tracking issue | only #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-check | 2026-10-30 | read-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 MoreLikeThis | upstream 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) |
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).
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | the minimal test fails (gradle; standalone -ea and -da) |
| 10.6.0, 10.5.2 (release jars) | affected | the minimal test fails with the same OutOfMemoryError |
| 1.9.1 to 10.5.2 | 21 releases | fail 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 commits | both fail; 81087e8bb66 with only the pre-size removed passes |
before 175cf8a9fd8; releases up to 1.4.3 | not affected | no retrieveInterestingTerms (no MoreLikeThis in any release before 1.9) |
fix (scratch worktree at 645d4fe14a20) | not affected | the minimal test and the whole queries suite pass (513 tests, 0 failures), in two fresh scratch worktrees |
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.
| Check | Verdict | What it found |
|---|---|---|
| finder (reference check, boundary probe) | candidate | JUnit 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, low | Own 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 | real | Built 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 | keep | The 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 validated | The 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). |
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.
- PNG 437 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,457 words
- 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-eaand-da, and with the fixed class shadowed together with the upstreamTestMoreLikeThis(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 mainea57360912ed, whereMoreLikeThis.javais 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.javais unchanged on upstream main2f424fd428da(2026-10-30): its newest commit is still448c7d8e2414(#15681, 2026-02-11).
- 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 @ 645d4fe14a20a 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 @ 645d4fe14a20JDK 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 @ 645d4fe14a20a Lucene main checkout lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java:80, 256-261, 588, 597, 721, 729 @ 645d4fe14a20a 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")
- TestMoreLikeThisInterestingTermsLargeMaxQueryTerms.java (lucene/queries, package org.apache.lucene.queries.mlt, 1 test):
repro/LFV-574/src/org/apache/lucene/queries/mlt/TestMoreLikeThisInterestingTermsLargeMaxQueryTerms.java(52 lines)
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.
assets/LFV-574/bug.png(437 KB)
![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).](../assets/LFV-574/bug.png)