The SortedSetDocValues counter sizes its top-children queue by the topN you pass, not by how many labels the dim can have, and Lucene's priority queue refuses 2,147,483,631 slots or more.
- Lucene counts facet labels in two main ways: taxonomy facets keep the labels in a side index; SortedSetDocValues (SSDV) facets keep them in a doc-values field of the main index. Both implement the same
FacetsAPI. getTopChildren(topN, dim)returns the topN labels of a dim with the highest counts. The counter picks them with a priority queue that holds at most topN entries. Some callers, Lucene's own taxonomy tests among them, passInteger.MAX_VALUEto mean "all children".- Lucene's
PriorityQueueallocates its whole array, topN + 1 slots, when it is created, and refuses sizes of 2,147,483,631 or more (Lucene's maximum array length,ArrayUtil.MAX_ARRAY_LENGTH).
getTopChildren(Integer.MAX_VALUE) makes SortedSetDocValues facets ask for a queue of 2,147,483,647 slots, which PriorityQueue refuses; taxonomy facets cap the queue at the taxonomy's size and return the 3 labels. Select the still to open it at full size. Generated with AI, checked against the code.- Index 3 documents with one SortedSetDocValues facet label each in dim
dim:a,b,c(defaultFacetsConfig). Count all hits withSortedSetDocValuesFacetCounts. - Ask for every label:
getTopChildren(Integer.MAX_VALUE, "dim"). The only topN check rejects topN <= 0, and taxonomy facets answer this call with all labels, so you expecta (1),b (1),c (1), asgetTopChildren(10, "dim")returns. - You get
IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647, with assertions on and off.getAllDims(Integer.MAX_VALUE),getTopDims(10, Integer.MAX_VALUE)and the concurrent counts fail the same way. - Why: to pick the top labels, the counter creates
new TopOrdAndIntQueue(topN)(AbstractSortedSetDocValueFacetCounts.java:325) without capping topN at the 3 labels. Lucene'sPriorityQueueallocates all topN + 1 slots up front and refuses 2,147,483,631 or more. Taxonomy facets build the same queue withMath.min(taxoReader.getSize(), topN). - Below that limit the call works but still reserves the slots:
getTopChildren(1 << 24, "dim")on the same 3 labels allocates 67,111,104 bytes, against 2,280 bytes at topN = 10 and 1,344 bytes on taxonomy facets. Close to the limit it can run out of heap instead.
Code using SortedSetDocValues facets, plain or concurrent, that passes Integer.MAX_VALUE (or any topN from 2,147,483,631) to getTopChildren, getAllDims or getTopDims to mean "all children": every such call on a dim with counts throws, at any index size, since 4.7.0. Large legal values reserve topN + 1 slots per dim.
Mechanism, terms and numbers571 words
SortedSetDocValues (SSDV) facets choose the top children of a dim with a priority queue that they size to the caller's topN, with no cap at the number of labels (AbstractSortedSetDocValueFacetCounts.java:325). Lucene's PriorityQueue allocates topN + 1 slots up front and refuses 2,147,483,631 or more. So getTopChildren(Integer.MAX_VALUE, dim), which Lucene's own taxonomy tests use to ask for every child, throws IllegalArgumentException on a dim with 3 labels as soon as one of its children has a count, and getAllDims(Integer.MAX_VALUE) and getTopDims(.., Integer.MAX_VALUE) fail the same way, in the plain and the concurrent counts. Facets.validateTopN accepts every topN > 0, and taxonomy facets cap the same queue with Math.min(taxoReader.getSize(), topN) and return every label. Below the limit the call returns the right labels but reserves the slots: 67,111,104 bytes for topN = 2^24 on 3 labels; close to the limit it can run out of heap instead. Default configuration, any index size, assertions on and off; 4.7.0 to 10.6.0 and main are affected (18 version points run, the releases in between by reading). Up to 4.6.1 the old API documented Integer.MAX_VALUE as "all children" and SSDV honoured it. The fix is one line: cap the queue at the field's label count, as taxonomy facets do.
getTopChildrenchecks topN withvalidateTopN(AbstractSortedSetDocValueFacetCounts.java:57), which rejects onlytopN <= 0(Facets.java:80-84), then callsgetTopChildrenForPath(:61) andcomputeTopChildren(:300-301).getAllDims(:112-140) does the same for every dim;getTopDimspassestopNChildrenthe same way (:189-191,:226).computeTopChildren(:309-339) walks the path's child ordinals and, at the first child with a count, creates the queue:q = new TopOrdAndIntQueue(topN);(:325). Nothing caps topN at the number of children or labels. With no counted child the queue is never built and the call returns normally.TopOrdAndIntQueueandTopOrdAndNumberQueuepass topN straight toPriorityQueue(TopOrdAndIntQueue.java:24,TopOrdAndNumberQueue.java:42). ThePriorityQueueconstructor throwsIllegalArgumentExceptionformaxSize >= ArrayUtil.MAX_ARRAY_LENGTH(PriorityQueue.java:121-124;Integer.MAX_VALUE - NUM_BYTES_ARRAY_HEADER, 2,147,483,631 here) and otherwise allocatesnew Object[maxSize + 1](:129,:134). Its javadoc says it pre-allocates that array (:34); the class is@lucene.internal(:39).SortedSetDocValuesFacetCountsandConcurrentSortedSetDocValuesFacetCountsboth extendAbstractSortedSetDocValueFacetCounts, so both fail;DrillSideways(executed) andMultiFacets(by reading) hand them out for SSDV dims.- Taxonomy facets build the same queue type as
new TopOrdAndIntQueue(Math.min(taxoReader.getSize(), topN))(TaxonomyFacets.java:250-251), and the other implementations cap too. The bound SSDV needs is at hand:SortedSetDocValuesReaderState.getSize(), "Number of unique labels", is 3 in the repro index.
| call | SSDV facets | taxonomy facets |
|---|---|---|
getTopChildren(10, "dim") | a (1), b (1), c (1); 2,280 bytes | - |
getTopChildren(1000, "dim") | a (1), b (1), c (1); 6,240 bytes | - |
getTopChildren(1 << 24, "dim") | a (1), b (1), c (1); 67,111,104 bytes | 1,344 bytes |
getTopChildren(2147483631, "dim") | IllegalArgumentException | - |
getTopChildren(Integer.MAX_VALUE, "dim") | IllegalArgumentException | a (1), b (1), c (1) |
getAllDims(Integer.MAX_VALUE) | IllegalArgumentException | the dim with its 3 labels |
with the fix, getTopChildren at 10, 1000, 1 << 24 or Integer.MAX_VALUE | a (1), b (1), c (1); 2,248 bytes | - |
with the fix, getTopChildren(2147483631, "dim") | a (1), b (1), c (1) | - |
with the fix, getAllDims(Integer.MAX_VALUE) | the dim with its 3 labels | - |
Allocation is the minimum over 5 calls after 3 warm-up calls (thread allocation counter). The planning probe on another index measured 381 MiB per dim at topN = 10^8.
Exposure and likelihood283 words
- Who is hit. Callers of
getTopChildren,getAllDimsorgetTopDims(.., topNChildren)onSortedSetDocValuesFacetCountsorConcurrentSortedSetDocValuesFacetCounts(also throughDrillSideways, executed, orMultiFacets, by reading, for SSDV dims) withInteger.MAX_VALUE, or any topN from 2,147,483,631 up. The call throws only when the dim or path has at least one child with a count; with none, the queue is never built (AbstractSortedSetDocValueFacetCounts.java:319-325). Code that moves from taxonomy to SSDV facets with that idiom, or shares it between both, meets it on the first such call. - Large but legal topN. Values such as 2^24 or 10^8 used as "all" work and return correct results, but reserve topN + 1 queue slots per dim with hits, per call: 67,111,104 bytes at 2^24 on 3 labels, 381 MiB per dim at 10^8. Values close to 2,147,483,631 can fail with
OutOfMemoryErrorinstead, depending on the heap: with a 1 GiB heap the hunt's harness gotOutOfMemoryError: Java heap spaceat topN = 2,147,483,630. - Default vs opt-in. Default
FacetsConfig, codec andIndexSearcher; no setting is needed. SSDV faceting is an opt-in API marked@lucene.experimental; the trigger is the topN value. - Who is not. The child queues of taxonomy facets and the other
Facetsimplementations, which cap topN (taxonomy'sgetTopDimsdimension queue is uncapped: the separate LFV-983). SSDV calls below 2,147,483,631 return the right labels as long as the heap holds topN + 1 slots. - Severity. Loud: an exception for that call; no wrong results, nothing written to the index.
- How likely. Rare: no public SSDV caller with the
Integer.MAX_VALUEidiom was found (downstream code uses it on taxonomy and range facets, which cap), and the searches found no public report since 4.7.0 (2014).
One JUnit class, one method, 3 documents, default FacetsConfig; besides Lucene's test framework it uses only public API. 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.facet.sortedset;
import org.apache.lucene.document.Document;
import org.apache.lucene.facet.FacetResult;
import org.apache.lucene.facet.Facets;
import org.apache.lucene.facet.FacetsCollector;
import org.apache.lucene.facet.FacetsCollectorManager;
import org.apache.lucene.facet.FacetsConfig;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;
public class TestSortedSetFacetsTopNMaxValue extends LuceneTestCase {
public void testGetTopChildrenWithMaxValueTopN() throws Exception {
FacetsConfig config = new FacetsConfig();
try (Directory dir = newDirectory()) {
try (IndexWriter w = new IndexWriter(dir, new IndexWriterConfig())) {
for (String label : new String[] {"a", "b", "c"}) {
Document doc = new Document();
doc.add(new SortedSetDocValuesFacetField("dim", label));
w.addDocument(config.build(doc));
}
}
try (DirectoryReader reader = DirectoryReader.open(dir)) {
FacetsCollector fc =
new IndexSearcher(reader).search(new MatchAllDocsQuery(), new FacetsCollectorManager());
Facets facets =
new SortedSetDocValuesFacetCounts(
new DefaultSortedSetDocValuesReaderState(reader, config), fc);
FacetResult result;
try {
result = facets.getTopChildren(Integer.MAX_VALUE, "dim");
} catch (IllegalArgumentException e) {
// expected: all 3 labels, as taxonomy facets return; actual: the child queue is sized topN
throw new AssertionError("expected all 3 labels of dim, got " + e, e);
}
assertEquals(3, result.childCount);
}
}
}
}
cp $REPRO/TestSortedSetFacetsTopNMaxValue.java $LUCENE/lucene/facet/src/test/org/apache/lucene/facet/sortedset/
cd $LUCENE && ./gradlew :lucene:facet:test --tests org.apache.lucene.facet.sortedset.TestSortedSetFacetsTopNMaxValue -Ptests.seed=DEADBEEF -Ptests.asserts=true
# assertions off: -Ptests.asserts=false (same failure)
- Expected
childCount3:a (1),b (1),c (1), asgetTopChildren(10, "dim")returns and as taxonomy facets give forInteger.MAX_VALUE.- Observed
IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647fromAbstractSortedSetDocValueFacetCounts.java:325, with assertions on and off.
More files, environment, full output292 words
The test indexes 3 documents with one SortedSetDocValuesFacetField("dim", label) each (a, b, c), counts MatchAllDocsQuery with SortedSetDocValuesFacetCounts and calls getTopChildren(Integer.MAX_VALUE, "dim"), expecting childCount 3. Single thread; every seed run gave the same failure (on main DEADBEEF and C0DECAFE with assertions on and off, 5EED1234 and 0DD5EED7 with them off, C0FFEE01 with them on; on the 10.6.0 jars DEADBEEF, C0DECAFE and 5EED1234 with both), although the test framework randomizes settings such as the Directory implementation. The IllegalArgumentException is caught once and rethrown as an AssertionError so that the message states expected against actual; the exception is kept as the cause. Set -Ptests.asserts explicitly: with seed DEADBEEF and no value, Lucene's build picks assertions off. Before it goes into a PR, ./gradlew tidy wraps one comment line that is longer than 100 characters; also use MatchAllDocsQuery.INSTANCE there, since new MatchAllDocsQuery() is deprecated on main and in 10.6.0 (the runs used the constructor; it does not affect the failure). The test needs the org.apache.lucene.tests framework and FacetsCollectorManager (recent releases); older releases were checked with release-portable main() programs.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT); 10.6.0 and every release run back to 4.7.0 also fail (the releases in between by reading); 4.3.0 to 4.6.1 pass |
|---|---|
| jdk | OpenJDK 25.0.3 |
| os | Linux aarch64 |
TestSortedSetFacetsTopNMaxValue > testGetTopChildrenWithMaxValueTopN FAILED
java.lang.AssertionError: expected all 3 labels of dim, got java.lang.IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647
at __randomizedtesting.SeedInfo.seed([DEADBEEF:3B208D4DB1BA11E3]:0)
at org.apache.lucene.facet.sortedset.TestSortedSetFacetsTopNMaxValue.testGetTopChildrenWithMaxValueTopN(TestSortedSetFacetsTopNMaxValue.java:56)
Caused by:
java.lang.IllegalArgumentException: maxSize must be >= 0 and < 2147483631; got: 2147483647
at org.apache.lucene.core@11.0.0-SNAPSHOT/org.apache.lucene.util.PriorityQueue.<init>(PriorityQueue.java:123)
at org.apache.lucene.core@11.0.0-SNAPSHOT/org.apache.lucene.util.PriorityQueue.<init>(PriorityQueue.java:80)
at org.apache.lucene.facet.TopOrdAndNumberQueue.<init>(TopOrdAndNumberQueue.java:42)
at org.apache.lucene.facet.TopOrdAndIntQueue.<init>(TopOrdAndIntQueue.java:24)
at org.apache.lucene.facet.sortedset.AbstractSortedSetDocValueFacetCounts.computeTopChildren(AbstractSortedSetDocValueFacetCounts.java:325)
at org.apache.lucene.facet.sortedset.AbstractSortedSetDocValueFacetCounts.getTopChildrenForPath(AbstractSortedSetDocValueFacetCounts.java:300)
at org.apache.lucene.facet.sortedset.AbstractSortedSetDocValueFacetCounts.getTopChildren(AbstractSortedSetDocValueFacetCounts.java:61)
at org.apache.lucene.facet.sortedset.SortedSetDocValuesFacetCounts.getTopChildren(SortedSetDocValuesFacetCounts.java:57)
at org.apache.lucene.facet.sortedset.TestSortedSetFacetsTopNMaxValue.testGetTopChildrenWithMaxValueTopN(TestSortedSetFacetsTopNMaxValue.java:53)
1 test completed, 1 failed
# the same with -Ptests.asserts=false; the 10.6.0 release jars fail the same way with -ea and -da- The only topN check,
validateTopN, rejects just topN <= 0; no javadoc sets an upper bound. - Taxonomy facets cap the same queue at the taxonomy's size, and Lucene's taxonomy tests call
getTopChildren(Integer.MAX_VALUE, ...); five more implementations cap too. - Up to 4.6.1 the API documented
Integer.MAX_VALUEas "all children" and SSDV honoured it; the 4.7.0 rewrite capped only taxonomy.
The full argument296 words
- The API accepts the value.
validateTopN"checks if topN is valid for getTopChildren and getAllDims" and rejects onlytopN <= 0(Facets.java:74-84); LUCENE-10467 (#751, 9.2) made that range consistent across implementations.getTopChildren"Returns the topN child labels under the specified path" (Facets.java:40-41). - The sibling implementation caps on purpose.
Math.min(taxoReader.getSize(), topN)came with the LUCENE-5339 rewrite (189feaacb6f, Michael McCandless), in the same commit that ported the oldInteger.MAX_VALUEtest to taxonomygetTopChildren(Integer.MAX_VALUE, "dim"); it is still on main (TestTaxonomyFacetCounts.java:556,TestTaxonomyFacetCounts2.java:338,:343).FloatTaxonomyFacets,StringValueFacetCounts,LongValueFacetCounts,RangeFacetCounts,RangeOnRangeFacetCountsandMatchingFacetSetsCountscap too. - A large top-n is the normal way to ask for every child. On #843 Yuting Gan (Yuti-G) wrote that callers keep getting all children "as long as the user specifies a large top-n"; on #974 Greg Miller (gsmiller) wrote that "getAllChildren is not identical behavior as getTopChildren with a huge top-n value", treating the latter as an ordinary call.
- History. Up to 4.6.1 the
FacetRequestjavadoc (Shai Erera, LUCENE-5136) said: "If set toInteger.MAX_VALUE, all immediate child categories will be returned", and Michael McCandless's SSDV accumulator (LUCENE-4795) answered any request for at least the dim's label count, soInteger.MAX_VALUEtoo, as "all available results" without a queue. The LUCENE-5339 rewrite (4.7.0) dropped that for SSDV only; none of its comments (90 in the JIRA record, 89 on the migrated #6403), no CHANGES or MIGRATE entry and no test discusses an uncapped SSDV queue. Executed: 4.6.1 returns all labels, 4.7.0 throws. - Core precedent. LUCENE-4938 (#6002) fixed an
IndexSearcherqueue that was not capped atmaxDoc(reported and fixed by Robert Muir; Michael McCandless and Uwe Schindler traced it), and on LUCENE-8542 (#9588) Michael McCandless called pre-allocating the full topN "really unexpected behavior".
The devil's-advocate check185 words
The strongest case for "works as designed": topN is a size the caller chooses; PriorityQueue documents that it pre-allocates and throws on purpose "to prevent confusing OOME"; Facets is @lucene.experimental; getAllChildren exists for "all children"; the sandbox TopnOrdinalIterator also sizes its queue from topN, with a TODO by its author Egor Potemkin (#13568) doubting the taxonomy getSize() cap for very large taxonomies; and the 4.7.0 redesign never repeated the "Integer.MAX_VALUE = all" promise.
Why it fails: validateTopN accepts the value; PriorityQueue is @lucene.internal and its message names an internal maxSize, not topN; getAllChildren is unordered ("Users should make no assumptions about ordering of the children") and has no getAllDims or getTopDims form, and both of those fail too; the same call works on taxonomy facets through a cap added in the same rewrite; and the TODO questions how loose that cap is, which argues for a tighter or lazily growing queue, not for failing. The redesign point is fair, so the history is best presented as a behaviour change in 4.7.0 rather than a broken promise. Verdict: not intended, a defect (low severity).
Recommended A: cap the child queue at the field's label count (validated)
Cap the queue at the field's label count, as taxonomy facets cap theirs at the taxonomy's size: new TopOrdAndIntQueue(Math.min(topN, state.getSize())) at AbstractSortedSetDocValueFacetCounts.java:325. With it the repro and the whole facet module test suite (307 tests) pass. The cap is loose when the field has many labels and the dim few.
if (q == null) {
// Lazy init, so we don't create this for the
// sparse case unnecessarily
// the path cannot have more children than the field has labels:
q = new TopOrdAndIntQueue(Math.min(topN, state.getSize()));
}All options and trade-offs4 options · 495 words
Option A was applied in two separate scratch worktrees of 645d4fe14a20 and run with gradle (both removed after the runs); the hunt's checks validated the same diff. Options B and C were not built.
One line in computeTopChildren (AbstractSortedSetDocValueFacetCounts.java:325), mirroring TaxonomyFacets.makeTopOrdAndNumberQueue (TaxonomyFacets.java:250-251).
if (q == null) {
// Lazy init, so we don't create this for the
// sparse case unnecessarily
// the path cannot have more children than the field has labels:
q = new TopOrdAndIntQueue(Math.min(topN, state.getSize()));
}Trade-offs
- A path's children are distinct ordinals of the field, so the queue never needs more than
state.getSize()slots; results are unchanged for every topN that works today. - Loose when the field holds many labels and the dim few: by default all dims share one field, so a 3-label dim in a field with millions of labels still gets a queue of that size for
Integer.MAX_VALUE(the same looseness as the taxonomy cap). The cross-model reviewer (OpenAI GPT-5.6) pointed this out.
What the checks validated
- Gradle, stock
645d4fe14a20(seed DEADBEEF), in two separate worktrees: the repro fails with-Ptests.asserts=trueandfalse. - Gradle with the fix, the whole facet module (
:lucene:facet:test, seed C0DE0994, assertions on): 40 classes (39 upstream and the repro), 307 tests, 0 failures, 7 skipped (tests a default run skips, e.g. the nightly group); the repro also passes with assertions off.:lucene:facet:checkGoogleJavaFormatpasses on the fix. - First run, with the fix: the repro and
TestSortedSetDocValuesFacets,TestDrillSideways,TestParallelDrillSideways,TestFacetQueryandTestFacetLabel, assertions on: 62 tests, 0 failures; the repro andTestSortedSetDocValuesFacetswith assertions off and nightly tests on: 20 tests, 0 failures, 0 skipped. - The executed probe with the fix:
Integer.MAX_VALUEreturns the 3 labels, and1 << 24allocates 2,248 bytes instead of 67,111,104. - The hunt's checks: the finder's 6 tests and an independent reproducer's 10 tests pass; 7 upstream facet test classes (82 tests) pass with this fix and the sibling LFV-983 fix; the differential harness's saturation and allocation failures drop to 0.
For a flat dim, ordRange.end() - ordRange.start() + 1; a hierarchical DimTree exposes only child iterators, so a child count would have to be computed or stored. Prompted by the cross-model reviewer's remark that A is loose.
Trade-offs
Removes the looseness of A; more code, and the hierarchical case needs new bookkeeping.
Start small and grow only as children arrive, instead of allocating topN + 1 slots up front.
Trade-offs
Would help every top-N facet path, but core PriorityQueue has a fixed capacity, so it needs a different structure; larger than the bug needs.
Add an upper bound to Facets.validateTopN.
Trade-offs
Turns the working taxonomy call getTopChildren(Integer.MAX_VALUE, ...), and Lucene's own tests of it, into errors.
Services that pass a user-chosen facet size straight to SortedSetDocValues facets should cap it: values from 2,147,483,631 up fail, and large values below that reserve heap in proportion to the value.
AI tools probing numeric limits passed Integer.MAX_VALUE as topN to SortedSetDocValues, taxonomy, string and long facets: only the SortedSetDocValues counts failed to return child labels (taxonomy's huge-topNDims failure is LFV-983). A model-based differential harness confirmed it on 40 random indexes; one minimal test reproduces it.
The method in full171 words
Method: non-formal first signal, then semi-formal checking (an executed probe, then model-based differential testing on the stock jars).
- A planning AI tool for numeric-limit hunts read the facet code and ran a probe (
FacetTopNProbe) on maina9779ef3c193, 10.5.2, 9.12.3 and the 10.6 release candidate: SSDVgetTopChildren,getAllDimsandgetTopDimswithInteger.MAX_VALUEthrew, and topN = 10^8 allocated 381 MiB per dim. - The hunting AI tool wrote a reference model of top-N facet results (saturation: for topN at least the number of children, the result equals the result at topN = 1000; allocation bounded by the labels) and a differential harness over 40 seeded random indexes, 18
Facetsconstructions and 5 call shapes: 93,485 checks, of which 9,924 saturation and 3,060 allocation checks failed on stock main (including the sibling dim-queue sites, LFV-983) and 0 with both fixes. An SMT solver confirmed that exactly topN from 2,147,483,631 to 2,147,483,647 throws. - It reduced the case to a public-API JUnit with a taxonomy control, and later to the one-test repro.
No prior report found in GitHub issues, PRs and commits (incl. the JIRA archive), the mailing lists, downstream trackers and code, the web, and all 414 open PRs; re-checked twice on 2026-10-30. Not fixed upstream: the file's last change is #16056 (2026-05-14, unrelated).
Searches5 searches
No report of this bug was found, and nothing upstream fixes it. The closest items are context: #6403 (LUCENE-5339, the rewrite that capped only the taxonomy queue); #843 and #974 (range-facet caps, and the remarks that a large top-n is how callers ask for every child); #133 and #11764 (other implementations' caps); #751 (the topN <= 0 check); #11586 (getAllChildren); #13568 (the sandbox TODO); #12862 (open, the only open PR touching the fix-site file; it does not touch the queue); the core precedents #6002 (LUCENE-4938) and #9588 (LUCENE-8542). The sibling LFV-983 is the same pattern at a different fix site, found by the same hunt.
| Where | When | How | Found |
|---|---|---|---|
| GitHub issues, PRs, commits (apache/lucene incl. the migrated JIRA archive; apache/lucenenet, apache/solr, OpenSearch, Elasticsearch) | 2026-10-28 to 2026-10-29 | three automated passes of 51 queries each (queue sizing; 'all children' with Integer.MAX_VALUE; the cap inconsistency with taxonomy) plus 30 manual searches, e.g. computeTopChildren, TopOrdAndIntQueue maxSize, getAllDims Integer.MAX_VALUE, getTopChildren IllegalArgumentException maxSize | no report; context only (#6403, #843, #974, #751, #11586, #13568) |
| JIRA (LUCENE, LUCENENET, SOLR), mailing lists, web, downstream code | 2026-10-29 | 22 JQL queries with 16 issues read in full; 24 lists.apache.org queries over dev@, java-user@, issues@; 18 web queries; GitHub code search for getTopChildren(Integer.MAX_VALUE / getAllDims(Integer.MAX_VALUE callers | no report; downstream code uses the idiom on taxonomy and range facets only; a 2016 java-user thread proposes it on taxonomy facets |
| Open PRs and recent activity | 2026-10-29 | changed-file lists of all 414 open apache/lucene PRs; the 93 items updated since 2026-10-26; fix-site commits on main, branch_11x, branch_10x and branch_10_0 to branch_10_6 | only #12862 touches the file, not the queue; nothing about facet top-N sizing |
| Re-check for this page | 2026-10-30 | issue/PR search for computeTopChildren, getTopChildren with Integer.MAX_VALUE, facet issues created since 2026-10-29; last commits on the fix-site file | nothing new; last change still #16056 (2026-05-14); upstream main 716d6f216f52 |
| Second re-check for this page | 2026-10-30 | issue/PR search for computeTopChildren, getTopChildren with Integer.MAX_VALUE, TopOrdAndIntQueue maxSize, SortedSetDocValuesFacetCounts topN, facet issues created on 2026-10-30; commits on the fix-site file, lucene/facet and PriorityQueue.java since 2026-10-28 | nothing new (the topN query finds only the unrelated #518 and the getTopDims PR #747); last change still #16056; upstream main 2f424fd428da |
Introduced by the LUCENE-5339 facet API rewrite (5458dbf2484, 2013-11-14), first released in 4.7.0. 4.3.0 to 4.6.1 return all labels for Integer.MAX_VALUE; 4.7.0 through 10.6.0 and main fail (18 version points run, the releases in between by reading). Only the exception type changes, with core PriorityQueue.
Versions tested9 rows
LUCENE-5339 "Simplify the facet module APIs" (#6403; Michael McCandless; feature-branch commit 5458dbf2484f, 2013-11-14; branch_4x port 7477fde1cb06 and trunk merge 9d0b60388da8, 2014-01-04; SVN-era patches, no PR) built the new SSDV child queue as new TopOrdCountQueue(topN) (renamed TopOrdAndIntQueue in f6cd7fda747). In the same series 189feaacb6f capped every taxonomy queue with Math.min(taxoReader.getSize(), topN) and ported the old Integer.MAX_VALUE test to taxonomy, and 92b1e230715 removed the old SSDV accumulator with its "all available results" branch. Later moves kept the expression: LUCENE-7811 (6.6.0, the concurrent counts), LUCENE-10325 (#747, 9.2.0, getTopDims), LUCENE-10585 (#915, 9.3.0, the shared base class).
The exception follows core PriorityQueue: IllegalArgumentException: maxSize must be <= 2147483391 in 4.7.0 to 4.9.0, NegativeArraySizeException: -2147483648 in 4.9.1 to 7.4.0 (LUCENE-5844 moved the + 1 ahead of the check), and the current message from 7.5.0 on (lucene-solr PR #411 by Raul Saavedra, merged by Michael McCandless). Versions from 4.3.0 to 10.5.2 and the 10.6.0 commit were run with release-portable main() programs on the same data (an SSDV dim and a taxonomy control with 3 labels); recent ones also with JUnit. All on OpenJDK 25.0.3, Linux aarch64. Releases between the executed ones are affected by reading.
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 (unreleased 11.0) | affected | fails, -ea and -da (gradle in two separate worktrees; standalone runner) |
10.6.0 (4ae946e5a9b9) | affected | fails, -ea and -da (minimal repro on the release jars, from computeTopChildren:332: the same expression, 7 lines lower than on main because main's #14761 is not in 10.6.0; history program 7 of 7 checks fail on the same commit) |
| 10.5.2, 9.12.3, 9.2.0 | affected | fail (history program 7 of 7, incl. getTopDims; JUnit 5 of 6 on 10.5.2 and 9.12.3) |
| 9.1.0, 8.11.4, 7.7.3, 7.5.0 | affected | fail, IllegalArgumentException (5 of 5) |
| 7.4.0, 6.6.6, 6.6.0 | affected | fail, NegativeArraySizeException, also in the concurrent counts (5 of 5) |
| 5.5.5, 4.10.4, 4.9.1 | affected | fail, NegativeArraySizeException (3 of 3) |
| 4.9.0, 4.7.0 | affected | fail, IllegalArgumentException 'maxSize must be <= 2147483391' (3 of 3); 64 MiB at 2^24 |
| 4.6.1, 4.6.0, 4.3.0 | not affected | pass: all 3 labels for Integer.MAX_VALUE (pre-4.7 API), about 5.5 KB at 2^24 |
| 4.2.1 | n/a | no SortedSetDocValues faceting |
The finder, an independent reproducer, an intended-behaviour devil's advocate, an adjudication, an audit, fact checks and two gradle runs for this page 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 verdicts522 words
Every executing check ran on the stock jars and voted real. 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 agreeing with the root cause and that the input is in contract.
| Check | Verdict | What it found |
|---|---|---|
| Planning probe | first signal | SSDV getTopChildren, getAllDims and getTopDims with Integer.MAX_VALUE throw on main, 10.5.2, 9.12.3 and the 10.6 release candidate; 381 MiB per dim at 10^8. |
| Finder (finder) | candidate | Differential harness (93,485 checks; its saturation and allocation failures drop to 0 with the fixes); JUnit 5 of 6 fail on main (-ea, -da), 10.5.2 and 9.12.3; taxonomy control passes; 6 of 6 with the fix. |
| Cross-model review, intended lens (OpenAI GPT-5.6, read-only) | real, medium | Raised the sandbox TODO as a counter-signal and judged it weaker than the production evidence; a separate re-run fails 5 of 6. |
| Independent reproducer | real | Own JUnit with flat, hierarchical and multi-valued dims in 2+ segments: 8 of 10 fail on main (-ea, -da), 10.5.2 and 9.12.3, including topN = 2,147,483,631; 67,110,272 vs 592 bytes; 10 of 10 with the fix. |
| Cross-model review, refute lens (OpenAI GPT-5.6, read-only) | not real, low (procedural) | It could not run any code, so it voted not real on procedure only; root cause agreed, input in contract; noted the fix's cap is loose. Separate re-runs fail 5 of 6. |
| Intended-behaviour devil's advocate | real, high | Built the strongest 'works as designed' case and rejected it; found the pre-4.7 'Integer.MAX_VALUE = all children' javadoc; re-runs fail on main and 10.5.2. |
| Adjudication | confirmed, high | Re-ran the independent reproduction's JUnit with a new seed: 8 of 10 fail with -ea and -da; controls pass. |
| History follow-up | 4.7.0 first affected | Release-portable programs: 4.3.0, 4.6.0 and 4.6.1 pass; 17 points from 4.7.0 to main fail. |
| Credibility audit | keep | Re-ran the finder's JUnit (-da, and -ea with a fresh seed): 5 of 6 fail; advised 'behaviour change in 4.7.0' wording. |
| Minimal repro | verified | One test fails on 645d4fe14a20 with -ea, -da and a second seed; passes with the fix. |
| Fact check of the first version | 2 narrow errors, fixed | Re-ran the gradle repro (-ea, -da); its own probe showed the concurrent, DrillSideways, hierarchical, multi-valued and MAX_ARRAY_LENGTH shapes throw and the fix returning all labels. |
| Fact check of the revised version | 3 narrow wording errors, fixed | Re-ran the gradle repro in a fresh worktree (-ea, -da; passes with the fix) and the repro on the 10.6.0 release jars with a new seed (-ea, -da); its own probe showed the plain, concurrent and DrillSideways calls throw on main and return the labels with the fix. |
| This page's first run | reproduced; fix validated | Gradle stock fails (-ea, -da); 10.6.0 release jars fail (-ea, -da); with the fix 62 tests, then 20 with nightly tests, pass; format check passes. |
| This page's second run | reproduced; fix validated | A fresh worktree: gradle stock fails (-ea, -da); with the fix the repro passes and the whole facet module (307 tests) passes; format check 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 506 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,523 words
- Registry title
- SortedSetDocValues facets size the child priority queue from topN instead of the number of labels, so getTopChildren, getAllDims and getTopDims(.., topNChildren) with topN = Integer.MAX_VALUE throw IllegalArgumentException where taxonomy facets return every label, and a large topN allocates topN + 1 queue slots even for a 3-label dim
- Area
- facet / SortedSetDocValues child queue (AbstractSortedSetDocValueFacetCounts.computeTopChildren, inherited by SortedSetDocValuesFacetCounts and ConcurrentSortedSetDocValuesFacetCounts)
- Priority
- Low, rank 643: SSDV getTopChildren and getAllDims fail loudly on the Integer.MAX_VALUE 'all children' idiom that taxonomy facets accept, a module bug that never returns wrong results, so it leads the new MAX_VALUE facet rows right after the GroupingSearch row LFV-964.
- Impact class
- wrong error
- Impact note
- Applications that call getTopChildren, getAllDims or getTopDims(.., topNChildren) on SortedSetDocValuesFacetCounts or ConcurrentSortedSetDocValuesFacetCounts (also through MultiFacets or DrillSideways for SSDV dims, by reading) with Integer.MAX_VALUE (or any topN >= 2147483631) as 'all children' get IllegalArgumentException 'maxSize must be >= 0 and < 2147483631' on every such call, where the same call on taxonomy facets returns every label, and with a large but legal topN pay a transient allocation of topN + 1 references per dim with hits however few labels exist (64 MiB at 2^24 on 3 labels); default FacetsConfig, codec and searcher, any index size, -ea and -da, every release from 4.7.0 to 10.6.0 and main; results for ordinary sizes are correct and nothing is written to the index.
- Summary
- You index a SortedSetDocValues facet dim with three labels and call getTopChildren(Integer.MAX_VALUE, dim) to get all of them, expecting the same result as getTopChildren(1000, dim), because Facets.validateTopN accepts every topN > 0 and taxonomy facets return all three labels for exactly this call. Instead AbstractSortedSetDocValueFacetCounts.computeTopChildren builds new TopOrdAndIntQueue(topN) without capping topN at the number of labels, so PriorityQueue throws IllegalArgumentException 'maxSize must be >= 0 and < 2147483631; got: 2147483647', and getAllDims(Integer.MAX_VALUE) and getTopDims(10, Integer.MAX_VALUE) fail the same way. Below that limit the same line pre-allocates topN + 1 slots: getTopChildren(2^24, dim) over 3 labels allocates about 64 MiB to return 3 results.
- Introduced
- 5458dbf2484f398b710ce40296d98e19969bf3c0 ('LUCENE-5339: current patch', lucene5339 feature branch, svn r1542025, Michael McCandless): the rewritten SortedSetDocValuesFacetCounts builds its child queue as 'q = new TopOrdCountQueue(topN)' (renamed TopOrdAndIntQueue by f6cd7fda747); released through the branch_4x port 7477fde1cb06 ('LUCENE-5371, LUCENE-5339: ... simplify facet APIs', svn r1555342) and the trunk merge 9d0b60388da8. Issue LUCENE-5339 'Simplify the facet module APIs' (#6403); SVN-era patches, no pull request [read: git log -S/-G on the queue expression and blame at the 4.7.0 tag in the history clone; the release boundary is executed: 4.6.1 passes, 4.7.0 fails]. A regression: the pre-4.7 code it replaced (SortedSetDocValuesAccumulator, LUCENE-4795, removed by 92b1e230715) returned all children without a queue when numResults covered the ordinal range, and the pre-4.7 FacetRequest javadoc documented Integer.MAX_VALUE as 'all'; in the same rewrite 189feaacb6f capped every taxonomy queue with Math.min(taxoReader.getSize(), topN) and ported the Integer.MAX_VALUE test to taxonomy only. Later moves kept the expression: LUCENE-7811 (3e425d39873, ConcurrentSortedSetDocValuesFacetCounts, 6.6.0) and LUCENE-10585 (8db1e41fc072, #915, merge into AbstractSortedSetDocValueFacetCounts, 9.3.0). (2013-11-14 (5458dbf2484, feature branch; author date equals commit date); 2014-01-04 (7477fde1cb06, reached branch_4x))
- First affected version
- 4.7.0 (tag 2014-02-25) [exec, -da: getTopChildren(Integer.MAX_VALUE) and getAllDims(Integer.MAX_VALUE) throw IllegalArgumentException 'maxSize must be <= 2147483391; got: 2147483647', getTopChildren(2^24) on 3 labels allocates 67,111,456 bytes, taxonomy control returns the 3 labels]; last unaffected 4.6.1 [exec: numResults = Integer.MAX_VALUE returns all children, 5,576 bytes at 2^24]; SortedSetDocValues faceting starts in 4.3.0 (4.3.0 and 4.6.0 also pass; 4.2.1 has no sortedset package)
- Affected versions
- every release from 4.7.0 to 10.5.2 (all 4.7.x-10.x lines), 10.6.0 (released 2026-10-29; run as its RC, branch_10_6 head 4ae946e5a9b9) and main a9779ef3c193; never fixed. [exec] follow-up history runs (release-portable main() on the core and facet jars, -da, JDK 25): 4.7.0, 4.9.0, 4.9.1, 4.10.4, 5.5.5, 6.6.0, 6.6.6, 7.4.0, 7.5.0, 7.7.3, 8.11.4, 9.1.0, 9.2.0, 9.12.3, 10.5.2, branch_10_6 and main all FAIL with the taxonomy control passing; the exception changes with core PriorityQueue (IllegalArgumentException 4.7.0-4.9.0, NegativeArraySizeException -2147483648 4.9.1-7.4.0, IllegalArgumentException 'maxSize must be >= 0 and < 2147483631' 7.5.0-main); ConcurrentSortedSetDocValuesFacetCounts is affected from 6.6.0 and getTopDims(.., topNChildren) from 9.2.0. The finder's JUnit on main (-ea, -da), 10.5.2 and 9.12.3; the independent reproduction's JUnit on main (-ea, -da), 10.5.2 and 9.12.3; the intended-behaviour check on main and 10.5.2. [read] the releases in between: git log -S/-G shows the uncapped queue expression unchanged in every SortedSetDocValues counts class since 2013 (moves only). Upstream main 645d4fe14a20 (2026-10-28) has no change at the fix site.
- 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
- non-formal: code reading; also reached by Z3
- Upstream and prior reports
- none found (2026-10-29, read-only; the prior-report record verdict new, plus the finder's search at 2026-10-28, the independent reproduction's and the intended-behaviour check's searches, the adjudication's and a recheck): three scripted prior-report searches (queue sizing, 'all children' with Integer.MAX_VALUE, cap inconsistency with taxonomy; 16 GitHub issue/PR, 3 commit, 2 downstream, 20 mailing-list over dev@, java-user@, issues@ and commits@, 4 JIRA-archive and 6 planned web queries each), 30 manual GitHub searches over apache/lucene, apache/lucenenet, apache/solr, OpenSearch and Elasticsearch, GitHub code search for downstream getTopChildren(Integer.MAX_VALUE / getAllDims(Integer.MAX_VALUE callers, 22 JIRA queries (LUCENE, LUCENENET, SOLR) with 16 issues read in full, 24 lists.apache.org queries, 18 web queries, the changed files of all 414 open apache/lucene PRs, all 93 items updated since 2026-10-26, and the fix-site commits on main, branch_11x, branch_10x and branch_10_0 to branch_10_6 by author and committer date. Recheck: 'computeTopChildren' and '"getTopChildren" "Integer.MAX_VALUE"' find no issue or PR; no item mentioning facet was updated since 2026-10-27; none of the 22 issues and PRs created since 2026-10-27 concerns facets (titles read); tracking issue #16937 does not mention facets; the last commit on AbstractSortedSetDocValueFacetCounts.java is dbf0d1426f13 (#16056, author and committer date 2026-05-14, an unrelated toArray cleanup); upstream main 645d4fe14a20 is 6 commits ahead of a9779ef3c193 and touches no lucene/facet file. Related, not this bug: #6403 (LUCENE-5339, the rewrite that capped only the taxonomy queue), #11574/#843 (LUCENE-10538) and #974 (LUCENE-10614, range-facet caps and remarks that a large top-n is the way to ask for all children), LUCENE-9950/#133 and #11764 (other implementations' caps), #11503/#751 (LUCENE-10467, the topN <= 0 check), #11586 (LUCENE-10550, getAllChildren), #12862 (open, the only open PR touching the fix-site file; does not touch the queue), #13568 (sandbox TopnOrdinalIterator, counter-signal TODO), and the core precedents #6002 (LUCENE-4938), #1581 (LUCENE-504), #9588 (LUCENE-8542), #7886 (LUCENE-6828).
- Lucene commit tested
- main
645d4fe14a20(2026-10-28; the fix-site file is unchanged on upstream main2f424fd428da, 2026-10-30): the minimal repro with gradle in two separate scratch worktrees, assertions on and off; with the fix, the repro and the whole facet module test suite (307 tests) and, in the first run, the SortedSetDocValues, drill-sideways, facet-query and facet-label test classes (assertions on) and the SortedSetDocValues test class with nightly tests (both worktrees removed after the runs); the standalone runner on main and on the 10.6.0 release jars with -ea and -da; an executed probe of the repro index (results and allocations, stock and with the fix); the earlier tests of the finder, an independent reproducer, a devil's advocate, an adjudication, an audit and fact checks on maina9779ef3c193(nolucene/facetchange up to645d4fe14a20),645d4fe14a20, 10.5.2 and 9.12.3; release-portablemain()programs on releases from 4.3.0 to 10.5.2 and the 10.6.0 commit. OpenJDK 25.0.3, Linux aarch64.
- Finding record
lucene/facet/src/java/org/apache/lucene/facet/sortedset/AbstractSortedSetDocValueFacetCounts.java:320-326 (q = new TopOrdAndIntQueue(topN)); Facets.java:38-44 (getTopChildren javadoc) and :76-84 (validateTopN rejects only topN <= 0); taxonomy/TaxonomyFacets.java:250-251 (Math.min(taxoReader.getSize(), topN)); lucene/core/src/java/org/apache/lucene/util/PriorityQueue.java:113-134
- The finding in 30 seconds
Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/sortedset/AbstractSortedSetDocValueFacetCounts.java:56-63, :112-140, :189-191, :226, :300-301, :309-339 (queue at :325)Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/Facets.java:39-44, :74-84; TopOrdAndIntQueue.java:23-25; TopOrdAndNumberQueue.java:41-43; taxonomy/TaxonomyFacets.java:250-251; sortedset/SortedSetDocValuesFacetCounts.java:57; sortedset/ConcurrentSortedSetDocValuesFacetCounts.java:52; sortedset/SortedSetDocValuesReaderState.java:164-165Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/util/PriorityQueue.java:34, :39, :79-80, :113-134; lucene/core/src/java/org/apache/lucene/util/ArrayUtil.java:32-34
- Who is affected
Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/sortedset/SortedSetDocValuesFacetCounts.java:55, sortedset/ConcurrentSortedSetDocValuesFacetCounts.java:50 (@lucene.experimental)Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/sortedset/AbstractSortedSetDocValueFacetCounts.java:58-60, :115-117 (no counts: null / empty list), :319-325 (queue built at the first counted child); taxonomy/TaxonomyFacets.java:610-612 (dim queue, LFV-983)
- Minimal repro
Lucene main 645d4fe14a20: lucene/core/src/java/org/apache/lucene/search/MatchAllDocsQuery.java:26, :31-34 (INSTANCE; the constructor is @Deprecated); the same at 4ae946e5a9b9 (10.6.0)
- Why it looks unintended
Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/Facets.java:33-34, :40-41, :74-84; taxonomy/TaxonomyFacets.java:250-251; taxonomy/TaxonomyReader.java:257-263 (getSize(): the number of categories, ancestors and the root included); taxonomy/FloatTaxonomyFacets.java:104; StringValueFacetCounts.java:180; LongValueFacetCounts.java:426; range/RangeFacetCounts.java:228; rangeonrange/RangeOnRangeFacetCounts.java:149; facetset/MatchingFacetSetsCounts.java:161Lucene main 645d4fe14a20: lucene/facet/src/test/org/apache/lucene/facet/taxonomy/TestTaxonomyFacetCounts.java:556; TestTaxonomyFacetCounts2.java:338, :343; lucene/core/src/java/org/apache/lucene/util/PriorityQueue.java:34, :39, :122; lucene/sandbox/src/java/org/apache/lucene/sandbox/facet/iterators/TopnOrdinalIterator.java:50-52a Lucene clone releases/lucene-solr/4.6.1: lucene/facet/src/java/org/apache/lucene/facet/search/FacetRequest.java:86-89; lucene/facet/src/java/org/apache/lucene/facet/sortedset/SortedSetDocValuesAccumulator.java:227-228
- Suggested fix
Lucene main 645d4fe14a20: lucene/facet/src/java/org/apache/lucene/facet/taxonomy/TaxonomyFacets.java:250-251; taxonomy/TaxonomyReader.java:257-263 (getSize(), the number of categories); sortedset/SortedSetDocValuesReaderState.java:47, :73-109
- History
a Lucene clone releases/lucene-solr/4.7.0: lucene/facet/src/java/org/apache/lucene/facet/sortedset/SortedSetDocValuesFacetCounts.java:120; taxonomy/IntTaxonomyFacets.java:102a Lucene clone: git diff 4ae946e5a9b9 645d4fe14a20 -- lucene/facet/src/java/org/apache/lucene/facet/sortedset/AbstractSortedSetDocValueFacetCounts.java (+5/-12; a7704f5242d #14761 on main, not an ancestor of 4ae946e5a9b9)
- TestSortedSetFacetsTopNMaxValue.java (JUnit for Lucene's build):
repro/LFV-994/src/org/apache/lucene/facet/sortedset/TestSortedSetFacetsTopNMaxValue.java(62 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-994/bug.png(506 KB)
