Both APIs size their result queue by the n you pass, not by how many hits the index can have, and Lucene's priority queue allocates all n + 1 slots at once.
- Lucene keeps the best n hits in a priority queue that holds at most n entries. Its
PriorityQueueallocates the whole array, n + 1 slots, when it is created, and refuses 2,147,483,631 slots or more. IndexSearcher.search(query, n)first caps n at the number of documents (maxDoc), so passingInteger.MAX_VALUEis a common way to ask for every hit; a core test checks that it works.RescoreTopNQueryre-scores another query's matches and keeps the top n (it was added to re-rank a kNN query with full-precision vectors).LatLonPoint.nearestreturns the n indexed points closest to a location.
RescoreTopNQuery and LatLonPoint.nearest ask for a queue sized n = 2,147,483,647 before collecting any hit, which PriorityQueue refuses; IndexSearcher.search caps n at maxDoc and returns the hit. Select the still to open it at full size. Generated with AI, checked against the code.- Index one document with one point,
new LatLonPoint("loc", 0, 0), and ask for every hit:searcher.count(new RescoreTopNQuery(new MatchAllDocsQuery(), DoubleValuesSource.constant(1), Integer.MAX_VALUE)), orLatLonPoint.nearest(searcher, "loc", 0, 0, Integer.MAX_VALUE). - You expect 1 hit, as
searcher.search(new MatchAllDocsQuery(), Integer.MAX_VALUE)gives:IndexSearchercaps n atmaxDoc, and nearest documents itself as equivalent to that search sorted by distance. - You get
IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647from both, with assertions on and off. - Why: both build a priority queue of n + 1 slots first (
RescoreTopNQuery.java:73,NearestNeighbor.java:254). Lucene'sPriorityQueueallocates every slot up front and refuses 2,147,483,631 or more. - Below that limit the calls work but pay for n: 400,002,296 bytes per call at n = 10^8 for 1 hit (the
IndexSearchercontrol: 1,160 bytes), andOutOfMemoryErrorat n = 3*10^8 under a 1 GiB heap.
Code that passes Integer.MAX_VALUE ("all") or any n from 2,147,483,631 to either API: every call throws, on any index. Large n below that cost 4 bytes per slot per call (compressed references; 400 MB at 10^8) or run out of heap. Default settings; both APIs are opt-in and niche, and results are never wrong.
Mechanism, terms and numbers560 words
RescoreTopNQuery(query, source, n) and LatLonPoint.nearest(searcher, field, lat, lon, n) return the top n hits and accept any n >= 1. Before they collect a single hit, each builds a Lucene PriorityQueue of n + 1 slots (RescoreTopNQuery.java:73, NearestNeighbor.java:254), which allocates every slot up front and refuses 2,147,483,631 or more. So n = Integer.MAX_VALUE, the usual way to ask for every hit, throws IllegalArgumentException on an index with a single document, and a large n below the limit costs 4 bytes per slot per call (compressed object references, as in the tested 1 GiB heap; 400 MB at n = 10^8 for 1 hit) or ends in OutOfMemoryError. IndexSearcher.search has capped the same request at maxDoc since LUCENE-2119 (Michael McCandless, 2009), still asserted by TestIndexSearcher.testHugeN, and nearest documents that it is "functionally equivalent" to that search sorted by distance. The fix caps each queue at what the index can return: maxDoc for RescoreTopNQuery, the number of indexed points for nearest (not maxDoc, because nearest returns a multi-valued document once per point). Default settings, any index; nearest is affected from 10.0.0, RescoreTopNQuery from 10.3.0, through 10.6.0 and main. Results are never wrong; the failure is loud.
RescoreTopNQuerydocuments "@param nthe number of documents to find" and anIllegalArgumentExceptiononly "ifnis less than 1" (RescoreTopNQuery.java:55-56), and the constructor checks only that (:58-61). The work happens inrewrite(:67-91), whichIndexSearcher.rewritecalls: before any candidate is visited it buildsHitQueue queue = new HitQueue(n, false);(:73), although the reader is in hand (:70). The class is@lucene.experimental(:34).LatLonPoint.nearest"Finds the n nearest indexed points", is "functionally equivalent to runningMatchAllDocsQuerywith aLatLonDocValuesField#newDistanceSort", and does not de-duplicate multi-valued fields, so a document can appear once per point (LatLonPoint.java:380-387). It rejects onlyn < 1(:409-411), collects each leaf'sPointValues(:418-430) and passes n on unchanged (:432-433). The package-privateNearestNeighborbuildsnew NearestHitQueue(n)(NearestNeighbor.java:254).- The queue.
HitQueueandNearestHitQueuepass the size toPriorityQueue(HitQueue.java:62-64,NearestNeighbor.java:80-83).PriorityQueuethrows formaxSize >= ArrayUtil.MAX_ARRAY_LENGTH("Throw exception to prevent confusing OOME",PriorityQueue.java:121-125), otherwise allocatesnew Object[maxSize + 1](:129,:134).MAX_ARRAY_LENGTHisInteger.MAX_VALUEminus the array header (ArrayUtil.java:33-34), 2,147,483,631 here. - The sibling that caps.
IndexSearcher.searchAftercaps numHits atMath.max(1, reader.maxDoc())(IndexSearcher.java:588,:597), and the sorted variant does too (:721,:729).TestIndexSearcher.testHugeN("should not throw exception",TestIndexSearcher.java:78-117) calls them withInteger.MAX_VALUE, with and without aSort.
Bytes allocated per call (thread allocation counter, minimum of 3 calls after 2 warm-ups):
| n | RescoreTopNQuery | LatLonPoint.nearest | IndexSearcher.search | with the fix (Rescore / nearest) |
|---|---|---|---|---|
| 1,000 | 6,296 | 10,992 | 1,160 | 2,296 / 6,992 |
| 10^7 | 40,002,296 | 40,006,992 | 1,160 | 2,296 / 6,992 |
| 10^8 | 400,002,296 | 400,006,992 | 1,160 | 2,296 / 6,992 |
| 3*10^8 | OutOfMemoryError: Java heap space | OutOfMemoryError: Java heap space | 1,160 | 2,296 / 6,992 |
| 2,147,483,630 | OutOfMemoryError: Java heap space | OutOfMemoryError: Java heap space | - | - |
Integer.MAX_VALUE | IllegalArgumentException | IllegalArgumentException | 1,160 | 2,296 / 6,992 |
Every call that returns finds the 1 hit. On one document with two points, nearest(..., 100) returns 2 hits, so nearest's cap has to be the point count, not maxDoc.
Exposure and likelihood212 words
- Who is hit. Callers of
new RescoreTopNQuery(..., n)orLatLonPoint.nearest(..., n)withInteger.MAX_VALUEor any n from 2,147,483,631 up: every call throws, on any index, even one with a single document. Also code that derives n from something large (an index-size estimate, a client's size parameter): about 400 MB per call at n = 10^8, andOutOfMemoryErrorabove what the heap holds (the executed runs show the calling operation failing; effects on other work in the same JVM were not tested). - Default vs opt-in. No setting is needed: default codec,
IndexWriterConfigandIndexSearcher. Both APIs are opt-in:RescoreTopNQuery(10.3.0 on,@lucene.experimental) was added to re-rank a kNN query with full-precision vectors (#14009);LatLonPoint.nearest(core since 10.0.0) is a geo top-n helper. - Scale. Only n matters, not the index size. Ordinary n (tens to thousands) costs kilobytes.
- Who is not.
IndexSearcher.searchandsearchAftercallers (capped), and any n the heap can hold, which returns correct results. - Severity. Loud: an exception for that call; no wrong results, nothing written to the index.
- How likely. Rare: the throw shows on the first call, so "all" is met in testing; default-branch code search found no use of either API in Solr, Elasticsearch or OpenSearch code, and the documented searches found no prior public report.
One JUnit class on Lucene's test framework, public API otherwise: one document with one point, two methods (one per API), each passing Integer.MAX_VALUE. It fails with assertions on and off and is also the suggested 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.search;
import java.io.IOException;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.LatLonPoint;
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.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;
public class TestTopNQueuePresizedByN extends LuceneTestCase {
public void testRescoreTopNQueryIntegerMaxValue() throws IOException {
try (Directory dir = new ByteBuffersDirectory();
DirectoryReader reader = oneDocIndex(dir)) {
Query q =
new RescoreTopNQuery(
new MatchAllDocsQuery(), DoubleValuesSource.constant(1), Integer.MAX_VALUE);
int count;
try {
count = new IndexSearcher(reader).count(q);
} catch (IllegalArgumentException e) {
throw new AssertionError("expected 1 hit for n = Integer.MAX_VALUE on 1 doc, got " + e, e);
}
assertEquals(1, count);
}
}
public void testLatLonPointNearestIntegerMaxValue() throws IOException {
try (Directory dir = new ByteBuffersDirectory();
DirectoryReader reader = oneDocIndex(dir)) {
TopFieldDocs hits;
try {
hits = LatLonPoint.nearest(new IndexSearcher(reader), "loc", 0, 0, Integer.MAX_VALUE);
} catch (IllegalArgumentException e) {
throw new AssertionError("expected 1 hit for n = Integer.MAX_VALUE on 1 doc, got " + e, e);
}
assertEquals(1, hits.scoreDocs.length);
}
}
private static DirectoryReader oneDocIndex(Directory dir) throws IOException {
try (IndexWriter w = new IndexWriter(dir, new IndexWriterConfig())) {
Document doc = new Document();
doc.add(new LatLonPoint("loc", 0, 0));
w.addDocument(doc);
}
return DirectoryReader.open(dir);
}
}
cp $REPRO/TestTopNQueuePresizedByN.java $LUCENE/lucene/core/src/test/org/apache/lucene/search/
cd $LUCENE && ./gradlew :lucene:core:test --tests org.apache.lucene.search.TestTopNQueuePresizedByN -Ptests.seed=DEADBEEF -Ptests.asserts=true
# assertions off: -Ptests.asserts=false (same failure)
- Expected
- 1 hit from each API, as
searcher.search(new MatchAllDocsQuery(), Integer.MAX_VALUE)returns on the same index. - Observed
IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647fromRescoreTopNQuery.java:73and fromNearestNeighbor.java:254, with assertions on and off.
More files, environment, full output329 words
The test indexes one document with LatLonPoint("loc", 0, 0) in a ByteBuffersDirectory with a default IndexWriterConfig. One method counts new RescoreTopNQuery(new MatchAllDocsQuery(), DoubleValuesSource.constant(1), Integer.MAX_VALUE) with IndexSearcher.count (the queue is built in rewrite); the other calls LatLonPoint.nearest(searcher, "loc", 0, 0, Integer.MAX_VALUE). Each expects 1 hit, which the fixed code returns. The class has two methods because the bug has two fix sites. Single thread; the outcome is the same for every seed run (DEADBEEF, 5EED0995). The IllegalArgumentException is caught once and rethrown as an AssertionError that states expected against actual, with the exception kept as the cause. Before it goes into a PR, use MatchAllDocsQuery.INSTANCE (the constructor is deprecated on main; it does not affect the failure); the format check already passes. The OutOfMemoryError range is not in the test (it needs a heap-dependent n); its numbers are in the table above.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT); the 10.6.0 release jars fail the same way; on 10.3.0 nearest fails with OutOfMemoryError: Requested array size exceeds VM limit instead (its failure before #16154) |
|---|---|
| jdk | OpenJDK 25.0.3 |
| os | Linux aarch64 |
TestTopNQueuePresizedByN > testRescoreTopNQueryIntegerMaxValue FAILED
java.lang.AssertionError: expected 1 hit for n = Integer.MAX_VALUE on 1 doc, got java.lang.IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647
at __randomizedtesting.SeedInfo.seed([DEADBEEF:167D491953B4EF13]:0)
at org.apache.lucene.search.TestTopNQueuePresizedByN.testRescoreTopNQueryIntegerMaxValue(TestTopNQueuePresizedByN.java:41)
Caused by:
java.lang.IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647
at org.apache.lucene.util.PriorityQueue.<init>(PriorityQueue.java:123)
at org.apache.lucene.search.HitQueue.<init>(HitQueue.java:63)
at org.apache.lucene.search.RescoreTopNQuery.rewrite(RescoreTopNQuery.java:73)
at org.apache.lucene.search.ConstantScoreQuery.rewrite(ConstantScoreQuery.java:44)
at org.apache.lucene.search.IndexSearcher.rewrite(IndexSearcher.java:873)
at org.apache.lucene.search.IndexSearcher.count(IndexSearcher.java:497)
at org.apache.lucene.search.TestTopNQueuePresizedByN.testRescoreTopNQueryIntegerMaxValue(TestTopNQueuePresizedByN.java:39)
TestTopNQueuePresizedByN > testLatLonPointNearestIntegerMaxValue FAILED
java.lang.AssertionError: expected 1 hit for n = Integer.MAX_VALUE on 1 doc, got java.lang.IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647
at __randomizedtesting.SeedInfo.seed([DEADBEEF:451FE0BBEB01E9C]:0)
at org.apache.lucene.search.TestTopNQueuePresizedByN.testLatLonPointNearestIntegerMaxValue(TestTopNQueuePresizedByN.java:54)
Caused by:
java.lang.IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647
at org.apache.lucene.util.PriorityQueue.<init>(PriorityQueue.java:123)
at org.apache.lucene.util.PriorityQueue.<init>(PriorityQueue.java:80)
at org.apache.lucene.document.NearestNeighbor$NearestHitQueue.<init>(NearestNeighbor.java:82)
at org.apache.lucene.document.NearestNeighbor.nearest(NearestNeighbor.java:254)
at org.apache.lucene.document.LatLonPoint.nearest(LatLonPoint.java:433)
at org.apache.lucene.search.TestTopNQueuePresizedByN.testLatLonPointNearestIntegerMaxValue(TestTopNQueuePresizedByN.java:52)
2 tests completed, 2 failed
# the same with -Ptests.asserts=false; the 10.6.0 release jars fail the same way (assertions off); on 10.3.0 nearest fails with OutOfMemoryError: Requested array size exceeds VM limit instead- Neither API documents an upper bound on n; nearest calls itself "functionally equivalent" to a sorted
MatchAllDocsQuerysearch, which acceptsInteger.MAX_VALUE. IndexSearcherhas capped n atmaxDocsince LUCENE-2119 (Michael McCandless; a 2.9.3 / 3.0.2 bug fix);testHugeNstill asserts it.- Not a designed limit: nearest threw
OutOfMemoryErroratInteger.MAX_VALUEup to 10.4.0 and throws the queue'sIllegalArgumentExceptionfrom 10.5.0.
The full argument306 words
- Contract.
RescoreTopNQuery: "n the number of documents to find", IAE only "if n is less than 1" (RescoreTopNQuery.java:55-56). nearest: "Finds the n nearest indexed points" and "functionally equivalent to runningMatchAllDocsQuerywith aLatLonDocValuesField#newDistanceSort" (LatLonPoint.java:380-384). That sorted search acceptsInteger.MAX_VALUEand returns every document (IndexSearcher.java:721-733; executed on the repro's document with aLatLonDocValuesFieldadded: 1 hit). The javadoc already excludes multi-valued de-duplication, so for single-valued documents the two differ exactly where the queue is sized. - Tests.
TestIndexSearcher.testHugeNasserts that the core top-n entry points acceptInteger.MAX_VALUE; using it as the governing precedent was first pointed out by the cross-model reviewer (an independent reviewer from another model family, OpenAI GPT-5.6). UpstreamTestRescoreTopNQueryuses small n only, andTestNearestnever asks for more than the number of indexed points. - History. LUCENE-2119 (#3195,
ae72952fa41, 2009, "behave better if you pass Integer.MAX_VALUE as nDcos [sic] to search methods";CHANGES.txtlists it under the 2.9.3 / 3.0.2 bug fixes, first released June 2010) added the cap toIndexSearcher; Michael McCandless wrote there: "This trips users up fairly often because they assume our PQ doesn't statically pre-allocate (a reasonable assumption...)". The same pattern was fixed at other sites: LUCENE-9614 (#413, Julie Tibshirani: "bound k by total number of vectors to prevent oversizing data structures") and #13184 (bugmakerrrrrr: kNN exact search caps itsHitQueueat the candidate count). The threads that added these two APIs (#14009, #14860, LUCENE-7069 / #8125, LUCENE-10266 / #897) contain no identified discussion of a large n. - Not a designed bound. For nearest, n =
Integer.MAX_VALUEgaveOutOfMemoryError: Requested array size exceeds VM limitup to 10.4.0 and gives thePriorityQueueIAE from 10.5.0, a side effect of #16154 ("Use Lucene's PriorityQueue in NearestNeighbor to replace poll/offer with updateTop"). The message names an internalmaxSize, not n.
The devil's-advocate check280 words
The strongest case for "works as designed" (built by an intended-behaviour devil's advocate and the adjudication): the LUCENE-2119 issue body itself calls Integer.MAX_VALUE "a nonsense value to pass in, since our PQ impl allocates the array up front"; Uwe Schindler wrote on java-user in 2009 "If you want to have all results, you do something wrong", and Simon Willnauer defended fixed-size top-k queues there in 2011; pre-allocation is documented on PriorityQueue, HitQueue and the collector managers, which take numHits literally; nearest's javadoc says it throws IllegalArgumentException if n is "out-of-bounds"; SOLR-5661 added the PriorityQueue guard on purpose and Shalin Shekhar Mangar accepted an error for a huge rows value there; RescoreTopNQuery is @lucene.experimental.
Why it fails: the same LUCENE-2119 then fixed the case as a bug, and testHugeN still asserts it. These two are convenience entry points like IndexSearcher.search, not collectors whose caller sizes the queue, and their javadocs mention no pre-allocation (PriorityQueue is @lucene.internal). "Out-of-bounds" came with the n >= 1 check in 2016 (ad86feca097), and no upper bound was ever defined. The guard only turns n >= 2,147,483,631 into an IAE; the OutOfMemoryError range below it has no guard. Willnauer defended fixed-size top-k queues and did not address capacity beyond what the index can return; the cap keeps the queue fixed-size. Honest weak spot: a maintainer may accept the IAE at Integer.MAX_VALUE as documented; the per-call allocation and the OOM on a one-document index do not depend on that reading. Verdict: not intended, a low-severity robustness defect. The claim is that the queue is not bounded by what the index can return, not that memory must follow the number of results.
Recommended A: cap RescoreTopNQuery at maxDoc and nearest at the indexed point count (validated)
Cap each queue at what the index can return: Math.min(n, Math.max(1, reader.maxDoc())) in RescoreTopNQuery.rewrite, and the summed PointValues.size() in LatLonPoint.nearest (not maxDoc: nearest returns a multi-valued document once per point). With it the repro and 2,839 tests of the core search and document packages pass.
// RescoreTopNQuery.rewrite (line 73)
HitQueue queue = new HitQueue(Math.min(n, Math.max(1, reader.maxDoc())), false);
// LatLonPoint.nearest: count the points in the existing leaf loop ...
long totalPoints = 0;
// ... totalPoints += points.size(); (next to totalHits += points.getDocCount();)
// ... and pass the capped n:
NearestNeighbor.nearest(latitude, longitude, readers, liveDocs, docBases,
(int) Math.min(n, Math.max(1, totalPoints)));All options and trade-offs5 options · 773 words
Option A was applied in a private scratch worktree of 645d4fe14a20 and run with gradle (worktree removed after the runs); the hunt's checks validated the same diff on a9779ef3c193. Options B to D were not built.
Two small edits: RescoreTopNQuery.java:73, and LatLonPoint.java:418-433 (sum PointValues.size() in the loop that already visits every leaf, then pass the capped n). The maxDoc cap is the one Michael McCandless gave IndexSearcher on LUCENE-2119 (IndexSearcher.java:588).
// RescoreTopNQuery.rewrite (line 73)
HitQueue queue = new HitQueue(Math.min(n, Math.max(1, reader.maxDoc())), false);
// LatLonPoint.nearest: count the points in the existing leaf loop ...
long totalPoints = 0;
// ... totalPoints += points.size(); (next to totalHits += points.getDocCount();)
// ... and pass the capped n:
NearestNeighbor.nearest(latitude, longitude, readers, liveDocs, docBases,
(int) Math.min(n, Math.max(1, totalPoints)));Trade-offs
- Results are unchanged for every n that works today; only the queue gets smaller.
maxDocis at mostIndexWriter.MAX_DOCS(Integer.MAX_VALUE - 128), below the queue limit, so the capped size always fits. - nearest is capped at the point count, not
maxDoc, because it returns a multi-valued document once per point: on one document with two points,nearest(..., 100)returns 2 hits (the finder's SMT obligation and a harness mutant found the same; the cross-model reviewers agreed). Points of deleted documents are counted, so it is an upper bound.NearestVisitoruses the same n to tell when the queue is full (NearestNeighbor.java:181); with the cap that happens only after every point was collected, so results do not change. - Still loose for
RescoreTopNQuerywhen the inner query matches few of many documents (see C). - Residual at extreme scale, not part of this bug: n =
Integer.MAX_VALUEover 2,147,483,631 or more points would still hit the guard; and under the draft long-doc-id change (#16847)maxDoccould exceed the array limit, so the cap would also need a clamp toArrayUtil.MAX_ARRAY_LENGTH - 1.
What the checks validated
- Gradle, stock
645d4fe14a20, seed DEADBEEF: the repro fails 2 of 2 with-Ptests.asserts=trueandfalse. - Gradle with the fix: the repro passes 2 of 2 with assertions on and off; the closest suites (
TestTopNQueuePresizedByN,TestRescoreTopNQuery,TestRescoreTopNQueryPrefetch,TestRescoreTopNQueryFloat16Prefetch,TestNearest,TestIndexSearcher; seed C0DE0995, assertions on): 6 classes, 27 tests, 0 failures, 3 skipped (1 by its own assumption, 2 nightly-onlyTestRescoreTopNQuerytests, which pass with the fix when the nightly group is enabled: 7 of 7); the wholeorg.apache.lucene.search.*andorg.apache.lucene.document.*test packages oflucene/core(seed C0DE0995, assertions on): 274 classes, 2,839 tests, 0 failures, 75 skipped (tests a default run skips, such as the nightly group).:lucene:core:checkGoogleJavaFormatpasses with the fix, with and without the repro file. - The allocation probe with the fix: every n from 1,000 to
Integer.MAX_VALUEreturns the 1 hit at 2,296 bytes (RescoreTopNQuery) and 6,992 bytes (nearest); on one document with two points nearest returns 2 hits for n = 100 and forInteger.MAX_VALUE. - The hunt's checks of the same diff: the finder's 4 tests pass (-ea, -da), upstream
TestRescoreTopNQuery,TestRescoreTopNQueryPrefetchandTestNearestrun 10 tests with 0 failures, an independent reproducer's 6 tests pass, and the bounded-exhaustive harness gives 3,081 checks with 0 failures.
Sum size() over the readers list that NearestNeighbor.nearest receives and cap there, next to the queue.
Trade-offs
Same effect as A for nearest: the class is package-private and LatLonPoint.nearest is its only caller.
Count or buffer the inner query's matches first and size the queue by that count.
Trade-offs
Tighter than A when a small candidate set sits in a big index (for example a kNN query with k = 100 over 10^7 documents still gets a 10^7-slot queue for Integer.MAX_VALUE under A), but needs a counting pass or a buffer; more code than the bug needs.
Start small and grow only as hits arrive, instead of allocating n + 1 slots up front.
Trade-offs
Would help every top-n path, but core PriorityQueue has a fixed capacity, so it needs a different structure on a hot path; larger than the bug needs.
A maxDoc cap for nearest drops hits for multi-valued fields. A documented upper bound on n turns the "all" idiom that IndexSearcher.search supports into an error, and helps the OutOfMemoryError range only if the bound is low.
Trade-offs
Both change results or the contract instead of only the queue size.
AI tools' boundary probes passed Integer.MAX_VALUE to many top-n APIs: these two threw, while IndexSearcher.search returned every hit. A later hunt built a differential harness over 39 small index layouts and SMT obligations, which added the out-of-memory range and showed that nearest must be capped at the point count. One minimal test reproduces it.
The method in full162 words
Method: semi-formal (the finder's label): executed boundary probes gave the first signal, then a bounded-exhaustive differential harness and SMT obligations on the stock jars.
- An accepted-parameter boundary probe (2026-10-26) saw both
IllegalArgumentExceptions at n =Integer.MAX_VALUEand noted them next to a kNN row, not filed separately. A planning probe for top-n APIs reproduced both on the stock main jars with controls passing. - The hunting AI tool wrote a differential harness (39 layouts of 1-3 segments, 1-2 points per document, 11 values of n; oracles: the API's own full result, the exact number of candidates or points, and an allocation bound) and 11 SMT bit-vector obligations. Every layout failed at n = 2,147,483,630 and 2,147,483,647 for both APIs; the obligations proved the
maxDoccap (RescoreTopNQuery) and the point-count cap (nearest) sound and gave the counterexample against amaxDoccap for nearest, which a harness mutant confirmed. - It reduced the case to public-API JUnits with controls, and later to the one-class repro.
No prior report found in GitHub issues, PRs and commits (incl. the JIRA archive), the mailing lists, downstream code, the web, and all 414 open PRs; re-checked on 2026-10-30. Not fixed upstream: main 2f424fd428da still has both lines.
Searches5 searches
No report of this bug was found, and nothing upstream fixes it. Closest items, not reports: #16926 (merged 2026-10-28: the empty-candidates fix in RescoreTopNQuery.rewrite, leaves the sizing), #16154 (changed nearest's exception, no cap discussed), #16847 (draft: long doc ids, edits all three files, no cap), #12162 (open: LatLonField.nearest would inherit the bug). Precedents at other sites: LUCENE-2119 (#3195), SOLR-5661, apache/lucene-solr#411 (the PriorityQueue guard, by rsaavedraf), #13184, LUCENE-9614 (#413), LUCENE-504 (#1581). Search gaps: GitHub search is phrase-like and does not index code; RescoreTopNQuery is being changed actively, so parallel PRs may appear after these searches.
| Where | When | How | Found |
|---|---|---|---|
| GitHub issues, PRs and commits (apache/lucene incl. the migrated JIRA archive; apache/lucene-solr, apache/solr, Elasticsearch, OpenSearch, OpenSearch k-NN, luceneutil) | 2026-10-28 to 2026-10-29 | five automated passes of 51 queries each and 55 manual phrasings: class and method names, the error message, MAX_VALUE, maxDoc, large n, recent issues and PRs | no report; context only (#14009, #14860, #16705, #16628, #16926, #8125, #11302, #16154) |
| Open PRs, upstream branches | 2026-10-29 | changed files of all 414 open apache/lucene PRs; commits on the fix-site files on main, branch_10_6 and branch_10x | 6 open PRs touch the files or PriorityQueue (#16847, #16851, #16277, #13936, #12162, #16911); none changes the queue sizing |
| Mailing lists, JIRA, web | 2026-10-29 | 18 lists.apache.org queries (dev@, java-user@, issues@) with 7 mails read; 5 JIRA issues read; 10 web queries | no report; precedents only (LUCENE-2119, SOLR-5661, LUCENE-7069, LUCENE-10266) |
| Recheck | 2026-10-29 | issues and PRs created since 2026-10-27 naming MAX_VALUE, maxSize, PriorityQueue, HitQueue, nearest or rescore; changed files of the 47 PRs updated since then | nothing new (only #16926 touches a fix-site file) |
| Re-check for this page | 2026-10-30 | gh search for RescoreTopNQuery, NearestNeighbor nearest, LatLonPoint.nearest, HitQueue maxSize, nearest Integer.MAX_VALUE, RescoreTopNQuery OutOfMemoryError; fix-site lines and last commits on upstream main; the 100 most recently updated open PRs | nothing new; upstream main 2f424fd428da still has both lines; only #16847 touches a fix-site file |
Not a regression: each API sized its queue from n from its first commit. nearest is affected since it moved to core in 10.0.0 (#897), RescoreTopNQuery since it was added in 10.3.0 (#14009); both through 10.6.0 and main. #16154 (10.5.0) only changed nearest's exception from OutOfMemoryError to IllegalArgumentException.
Versions tested8 rows
LatLonPoint.nearest and its n-sized hit queue came from LUCENE-7069 (f6c7fc7a265, Mike McCandless, 2016, sandbox; ad86feca097 added the n >= 1 check, the "out-of-bounds" @throws text and the multi-valued note a day later) and moved to core unchanged with LUCENE-10266 (#897, fcd98fd3370, Rushabh Shah, 2022-06-13, main only), first released in 10.0.0. RescoreTopNQuery was added by #14009 (3404496b3d7, Dzung Bui, 2025-06-28; backport #14860, 4fcb6d6d50d), first released in 10.3.0, with new HitQueue(n, false) from the start. #16154 (Prithvi S, backport d0cb4f5e20f, first in 10.5.0) switched NearestNeighbor to Lucene's PriorityQueue, which changed nearest's n = Integer.MAX_VALUE failure from OutOfMemoryError: Requested array size exceeds VM limit to the queue's IAE; n = 2,147,483,630 gives OutOfMemoryError: Java heap space in each release where it was run. #16926 (2026-10-28) changed RescoreTopNQuery.rewrite below the queue line only. The sandbox predecessor of nearest (6.1.0 to 9.12.3, out of scope) has the same queue. The changes are cited as the sources of the code, not as blame.
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 (unreleased 11.0) | affected | both fail, -ea and -da (gradle in a scratch worktree; standalone runner); upstream main 2f424fd428da has the same lines |
10.6.0 (4ae946e5a9b9) | affected | both fail (minimal repro on the release jars) |
| 10.5.2, 10.5.0 | affected | 10.5.2: both fail; 10.5.0: nearest fails with the IAE at n = MAX (from #16154), RescoreTopNQuery affected by tag containment |
| 10.4.0 | affected | nearest fails with OutOfMemoryError 'Requested array size exceeds VM limit' at n = MAX; RescoreTopNQuery affected by tag containment |
| 10.3.0 | affected | both fail (first release with RescoreTopNQuery) |
| 10.2.2 | nearest only | nearest affected (10.2.0 fails); no RescoreTopNQuery (compile error) |
| 10.0.0 | affected | nearest fails (first release with core nearest) |
| 9.12.3 | n/a | no core nearest (compile error); the sandbox copy, out of scope, fails |
The finder, an independent reproducer, a devil's advocate, an adjudication, an audit, a registry re-run and this page's gradle run all reproduce it. Cross-model reviews (OpenAI GPT-5.6) could run nothing: one said real, the other not real for procedure only.
Checks and verdicts371 words
Every executing check ran on the stock jars and agreed. The only dissent is procedural: a cross-model refute review that could run no code voted not real on procedure only at low confidence while its content findings supported the bug. Fact checks of this page re-checked its claims (one re-ran the repro, the release runs, the fix and the probe; one cross-model, read-only); their wording fixes are applied. The read-only one also judged the bug not worth claiming (extreme n, loud failure), which the executing one disputed.
| Check | Verdict | What it found |
|---|---|---|
| Finder | candidate | JUnits fail 4 of 4 on main (-ea, -da), the 10.6 release candidate, 10.5.2 and 10.3.0; heap probe under 1 GiB; harness over 39 layouts; 11 SMT obligations; the fix passes everything. |
| Cross-model review, intended lens (OpenAI GPT-5.6, read-only) | real, medium | Weighed 'top-n needs O(n) storage' and the pre-allocating queue and found them not holding; pointed to testHugeN; agreed nearest needs the point-count cap. |
| Independent reproducer | real | Own tests (7 documents, two segments) fail 4 of 6 on main, 10.5.2, 10.3.0 and 10.2.0 (nearest); controls pass; all 6 pass with the fix. |
| Cross-model adversary, refute lens (OpenAI GPT-5.6, read-only) | not real, low (procedural) | Could not run anything, so it voted not real on procedure only; its content findings support the bug. A separate run reproduced both exceptions and 10 hits for 5 documents from nearest. |
| Intended-behaviour devil's advocate | real | Built the 'nonsense value', misuse, pre-allocation, SOLR-5661 and out-of-bounds case and rejected it; reproduced on main and 10.0.0; corrected the first release to 10.0.0. |
| Adjudication | confirmed, high | Re-ran the independent reproduction's tests with a new seed on main (-ea, -da): 4 of 6 fail with the same exceptions, controls pass. |
| Credibility audit | keep | Re-ran the finder's tests and an own probe (3 documents, 2 points each): 6 hits from nearest for 3 documents, the IAE and OOM in both APIs. |
| This page's run | reproduced; fix validated | A private worktree: gradle stock fails (-ea, -da); with the fix the repro, the closest suites and 2,839 tests of the search and document packages pass; format check passes; the standalone runner fails on main, 10.6.0 and 10.3.0. |
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 497 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,505 words
- Registry title
- RescoreTopNQuery and LatLonPoint.nearest size their priority queue from the caller's n, not from what the index can return, so n = Integer.MAX_VALUE ('all') throws IllegalArgumentException and a large n throws OutOfMemoryError, even on an index of a few documents
- Area
- search / top-n rescoring and geo nearest search (queue sizing: RescoreTopNQuery.rewrite; LatLonPoint.nearest -> NearestNeighbor)
- Priority
- Low, rank 645: RescoreTopNQuery and LatLonPoint.nearest throw IAE or OOM for a huge n but return correct results otherwise, core code with niche APIs affected only since 10.0 and 10.3, so it closes the MAX_VALUE facet group after LFV-983 and above LFV-265.
- Impact class
- crash
- Impact note
- Applications on 10.0.0 (LatLonPoint.nearest) or 10.3.0 (RescoreTopNQuery) through 10.6.0 and main that pass n = Integer.MAX_VALUE to mean 'all', or forward a large caller-chosen size, get an IllegalArgumentException (n >= 2147483631) or an OutOfMemoryError (n = 3*10^8 under a 1 GB heap) from inside the search instead of every candidate, on any index (even a few documents) and with default settings; an n far above maxDoc still allocates n queue slots per call (38 MiB at n = 10^7 in the finder's harness), results are never wrong, and both APIs are niche (RescoreTopNQuery is @lucene.experimental).
- Summary
- You search with new RescoreTopNQuery(query, source, Integer.MAX_VALUE), or call LatLonPoint.nearest(searcher, field, lat, lon, Integer.MAX_VALUE), to get every candidate, and expect them all back, as n = 1000 (or 100) gives on a 9-document index and as nearest's documented equivalent, a MatchAllDocsQuery search sorted by distance, gives for Integer.MAX_VALUE. Instead both build a priority queue of n + 1 slots before looking at the index: Integer.MAX_VALUE throws IllegalArgumentException('maxSize must be >= 0 and < 2147483631') from inside the search, and a large n below that throws OutOfMemoryError (n = 3*10^8 under a 1 GB heap). IndexSearcher.search has capped the same request at maxDoc since LUCENE-2119, as Michael McCandless proposed there; the analogous fix caps RescoreTopNQuery's queue at maxDoc and nearest's at the number of indexed points (not maxDoc, because nearest returns a multi-valued document once per point).
- Introduced
- fcd98fd3370b on main (#897, LUCENE-10266 'Move nearest-neighbor search on points to core', Rushabh Shah / shahrs87, merged 2022-06-13, main only, first released in 10.0.0) moved LatLonPoint.nearest and its n-sized hit queue into core unchanged; the queue code was written in f6c7fc7a2658 (LUCENE-7069 / #8125, Mike McCandless, 2016-04-14, sandbox, out of scope), and ad86feca097 (2016-04-15) added the n >= 1 check and the equivalence javadoc. RescoreTopNQuery.rewrite's 'new HitQueue(n, false)' came with 3404496b3d77 (#14009, Dzung Bui / dungba88, author date 2025-06-28; branch_10x backport 4fcb6d6d50d, #14860, merged 2025-06-29) [read: git blame on main and the history of both files; exec: release boundaries, 9.12.3 has no core nearest and 10.2.2 no RescoreTopNQuery (compile errors), 10.0.0 and 10.3.0 fail]. Neither site is a regression: each sized its queue from n from its first commit. #16154 (f5622b061ce, Prithvi S / iprithv, 2026-06-01, first in 10.5.0) only changed nearest's n = Integer.MAX_VALUE failure from OutOfMemoryError to IllegalArgumentException. (2022-06-13 (#897, core LatLonPoint.nearest); 2025-06-28 (#14009, RescoreTopNQuery; backport #14860 merged 2025-06-29); the sandbox queue code 2016-04-14 (LUCENE-7069))
- First affected version
- 10.0.0 [exec] for LatLonPoint.nearest (first release with #897; 9.12.3 has no core nearest [exec: compile error]); 10.3.0 [exec] for RescoreTopNQuery (10.2.2 has no such class [exec: compile error])
- Affected versions
- LatLonPoint.nearest: 10.0.0, 10.1.0, 10.2.0-10.2.2, 10.3.0-10.3.2, 10.4.0, 10.5.0-10.5.2, 10.6.0 (released 2026-10-29; run as its RC, branch_10_6 4ae946e5a9b) and main a9779ef3c193 [exec: 10.0.0, 10.2.0, 10.3.0, 10.4.0, 10.5.0, 10.5.2, the RC, main -ea and -da; tag containment for the rest]; RescoreTopNQuery: 10.3.0-10.3.2, 10.4.0, 10.5.0-10.5.2, 10.6.0 and main [exec: 10.3.0, 10.5.2, the RC, main -ea and -da]. At n = Integer.MAX_VALUE nearest throws OutOfMemoryError 'Requested array size exceeds VM limit' up to 10.4.0 and IllegalArgumentException from 10.5.0 (#16154); n = 2147483630 gives OutOfMemoryError 'Java heap space' in every affected release. The out-of-scope sandbox predecessor of nearest (6.1.0-9.12.3) has the same queue [exec: 6.1.0, 9.12.3]. Not fixed upstream: upstream main 645d4fe14a20 still has both lines (2026-10-29) [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; also reached by fuzzing/PBT, exhaustive check
- Upstream and prior reports
- none found (the prior-report record verdict new, searched 2026-10-29, read-only: three scripted prior-report searches of 51 queries each over GitHub issues, PRs and commits, downstream repositories, the dev@, java-user@, issues@ and commits@ lists, the JIRA archive and the web, plus the finder's two runs; 55 GitHub search phrasings over apache/lucene, apache/lucene-solr, apache/solr, elastic/elasticsearch, opensearch-project/OpenSearch, opensearch-project/k-NN and luceneutil; the changed files of all 414 open apache/lucene PRs (6 touch the fix-site files or PriorityQueue: #16847, #16851, #16277, #13936, #12162, #16911; none changes the queue sizing); the upstream commits of the 5 files on main, branch_10_6 and branch_10x; 18 list queries with 7 mails read; 5 JIRA issues; 10 web queries; 35 hits read, none the same bug; recheck 2026-10-29: gh search for RescoreTopNQuery, NearestNeighbor and 'LatLonPoint nearest', issues and PRs created since 2026-10-27 naming MAX_VALUE, maxSize, PriorityQueue, HitQueue, nearest or rescore (1 unrelated hit, #16932), the 13 issues updated since 2026-10-27 (none names either class) and the changed files of the 47 PRs updated since 2026-10-27 (only #16926 touches a fix-site file); two searches hit the shared rate limit after retries and were replaced by those REST listings); not fixed upstream (upstream main 645d4fe14a20 still has 'new HitQueue(n, false)' at RescoreTopNQuery.java:73 and 'new NearestHitQueue(n)' at NearestNeighbor.java:254, 2026-10-29); closest items, not reports: #16926 (Greg Miller, merged 2026-10-28, author date 2026-10-28: fixes the empty-candidates crash LFV-297 and leaves the sizing), #16154 (iprithv, 2026-06-01: moved NearestNeighbor to Lucene's PriorityQueue, changing the n = MAX failure, no cap discussed), #14009 and #14860 (introduced RescoreTopNQuery; no queue-size discussion), #8125 (LUCENE-7069) and #11302 (LUCENE-10266), #16847 (jimczi, draft, long doc ids: edits all three files, no cap), #12162 (open: LatLonField.nearest would inherit the bug); precedents at other sites: LUCENE-2119 (#3195, IndexSearcher caps n at maxDoc), SOLR-5661, apache/lucene-solr#411 (the PriorityQueue guard n = MAX trips today), #13184 (kNN exact search caps its HitQueue at the candidate count), LUCENE-9614 (#413)
- Lucene commit tested
- main
645d4fe14a20(2026-10-28; the three fix-site lines are unchanged on upstream main2f424fd428da, 2026-10-30): the minimal repro with gradle in a private scratch worktree, assertions on and off, stock and with the fix; with the fix, the closest suites and the wholesearchanddocumenttest packages oflucene/core(2,839 tests) and the format check (worktree removed after the runs); the standalone runner on main (-ea, -da) and on the 10.6.0 and 10.3.0 release jars; an allocation probe of the repro index, stock and with the fix. The finder, an independent reproducer, a devil's advocate and an adjudication ran their own tests on maina9779ef3c193(the fix-site files unchanged up to645d4fe14a20except #16926: the empty-candidates check below the queue and a local-variable rename) and on releases from 10.0.0 to 10.6.0. OpenJDK 25.0.3, Linux aarch64.
- Finding record
lucene/core/src/java/org/apache/lucene/search/RescoreTopNQuery.java:34 (at a9779ef3c193) (@lucene.experimental), :49-60 ('@param n the number of documents to find', '@throws IllegalArgumentException if n is less than 1', only n < 1 rejected), :73 ('HitQueue queue = new HitQueue(n, false);')lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java:380-406 (at a9779ef3c193) ('Finds the n nearest indexed points', 'functionally equivalent to running MatchAllDocsQuery with a LatLonDocValuesField#newDistanceSort', 'Multi-valued fields are currently not de-duplicated', '@throws IllegalArgumentException if ... n are out-of-bounds'), :409-410 (only n < 1 rejected), :432-433 (n passed on uncapped)lucene/core/src/java/org/apache/lucene/document/NearestNeighbor.java:254 (at a9779ef3c193) ('final NearestHitQueue hitQueue = new NearestHitQueue(n);')lucene/core/src/java/org/apache/lucene/util/PriorityQueue.java:113-136 (at a9779ef3c193) (the maxSize guard 'to prevent confusing OOME' and heap = new Object[maxSize + 1])lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java:588 (at a9779ef3c193) and :721-733 (numHits capped at Math.max(1, reader.maxDoc())); lucene/core/src/test/org/apache/lucene/search/TestIndexSearcher.java:79 (testHugeN)
- The finding in 30 seconds
Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/search/RescoreTopNQuery.java:34, :55-61, :67-91 (queue at :73, reader at :70)Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/document/LatLonPoint.java:380-387, :409-411, :418-433; document/NearestNeighbor.java:35, :80-83, :254Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/search/HitQueue.java:57, :62-64; util/PriorityQueue.java:34, :39, :121-134; util/ArrayUtil.java:33-34Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java:588, :597, :721, :729; lucene/core/src/test/org/apache/lucene/search/TestIndexSearcher.java:78-117 (testHugeN)
- Who is affected
Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/search/RescoreTopNQuery.java:26-34 (class javadoc, @lucene.experimental)
- Minimal repro
Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/search/MatchAllDocsQuery.java (INSTANCE; the constructor is deprecated)
- Why it looks unintended
Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/search/RescoreTopNQuery.java:34, :55-56; document/LatLonPoint.java:380-384, :399-400; search/IndexSearcher.java:721-733; util/PriorityQueue.java:39Lucene main 645d4fe14a20: lucene/core/src/test/org/apache/lucene/search/TestIndexSearcher.java:78-117a Lucene clone: git log -1 ae72952fa41 (LUCENE-2119, 2009-12-06, Michael McCandless)
- Suggested fix
Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/search/IndexSearcher.java:588; index/IndexWriter.java:208 (MAX_DOCS); document/NearestNeighbor.java:35, :181; util/ArrayUtil.java:33-34
- History
a Lucene clone: git log -1 fcd98fd3370 / 3404496b3d7 / 4fcb6d6d50d; git tag --contains fcd98fd3370 (from releases/lucene/10.0.0), 4fcb6d6d50d (from releases/lucene/10.3.0), d0cb4f5e20f (from releases/lucene/10.5.0)
- TestTopNQueuePresizedByN.java (JUnit for Lucene's build):
repro/LFV-995/src/org/apache/lucene/search/TestTopNQueuePresizedByN.java(68 lines)
Made in parallel with this page: a summary card (PNG), built from facts checked against the code, the minimal repro and an executed probe of the same index.
assets/LFV-995/bug.png(497 KB)
