A Lucene field holds its value as bytes, and Field.setBytesValue swaps those bytes so one field object can be reused for many documents. The range doc-values fields also keep the arrays they were built from, and their getters answer from those arrays, not from the bytes.
setBytesValue replaced with [10, 20]; getMin/getMax read the arrays the constructor kept, still [1, 2]. Select the still to open it at full size. Generated with AI, checked against the code.- You build
new LongRangeDocValuesField("r", new long[] {1}, new long[] {2})and reuse it for the next document:setBytesValuewith the encoding of [10, 20] fromLongRange.verifyAndEncode. This is the reuse idiomFielddocuments and Lucene's own facet tests use on this class. - You expect
getMin(0)= 10 andgetMax(0)= 20. The index does get [10, 20]: a doc-values query for [15, 15] matches the document, one for [1, 2] does not. - Instead
getMin(0)returns 1 andgetMax(0)returns 2. The constructor kept itsmin/maxarrays, the getters read only those, andsetBytesValuereplaces only the bytes. - Int/Float/DoubleRangeDocValuesField do the same. A
LatLonShapeDocValuesFieldbuilt for a point and given a 3-point line's bytes still reports type POINT, 1 term and the point's centroid, instead of LINE, 2 terms and the line's centroid.
Applications that reuse a range or shape doc-values field through setBytesValue and then read that object's getters, for example to log the value or derive another field. No setting is involved. The indexed doc value is correct, and no Lucene code reads these getters, so it is rare and minor.
Mechanism, terms and numbers463 words
Field.setBytesValue is the documented way to reuse one field object for many documents, and the only reuse path the range doc-values fields have. After it, Long/Int/Float/DoubleRangeDocValuesField.getMin/getMax still return the constructor's range while the index stores the new one: built for [1, 2] and given the bytes of [10, 20], getMin(0) returns 1 and getMax(0) returns 2. LatLon/XYShapeDocValuesField likewise keep the old shape's centroid, bounding box, term count and dimension type. The indexed doc value, the only thing these fields index, takes the new bytes (an executed control reads it back and runs a slow intersects query), and no Lucene code reads these getters, so only applications that read the getters of a reused field object are hit. In every release since 8.2.0 (range) and 9.4.0 (shape).
- The constructor keeps the arrays.
LongRangeDocValuesField(String, long[], long[])encodesmin/maxinto the bytes it passes to the base class and also keeps the caller's arrays infinalfields, by reference (LongRangeDocValuesField.java:27-29,:32-38). - The getters read only those arrays.
getMin/getMax("Get the minimum/maximum value for the given dimension") returnmin[dimension]/max[dimension](:40-56, the fix site). Int/Float/Double are identical. setBytesValuereplaces only the bytes. Neither the four classes nor their package-private baseBinaryRangeDocValuesField(:22-37) override it, so the inheritedField.setBytesValue(BytesRef)setsfieldsData = valueand nothing else (Field.java:335-346).- Indexing reads the bytes. The doc-values writer takes
field.binaryValue()(IndexingChain.java:1741-1742), which isfieldsData(Field.java:428-434). So the index gets [10, 20] while the getters say [1, 2]. That binary doc value is all these fields index (BinaryDocValuesField.java:44,ShapeDocValuesField.java:49). - Shapes, same pattern.
ShapeDocValuesFielddecodes the shape once intoprotected final ShapeDocValues shapeDocValues(ShapeDocValuesField.java:43,:54-58).numberOfTerms(:85-87),getHighestDimensionType(:107-109) and the LatLon/XYgetCentroid/getBoundingBox(LatLonShapeDocValuesField.java:66-75,XYShapeDocValuesField.java:66-74) read that cached decode; there is nosetBytesValueoverride.
- Long range field built for [1, 2], given the bytes of [10, 20]:
binaryValue()decodes to 10..20, the indexed doc value decodes to 10..20, anewSlowIntersectsQueryfor [15, 15] counts 1 and one for [1, 2] counts 0;getMin(0)= 1,getMax(0)= 2. A field built directly for [10, 20] reports 10 and 20. - Int/Float/Double range fields, same steps: 1/2, 1.0/2.0, 1.0/2.0.
LatLonShapeDocValuesFieldfor the point (0, 0) given the bytes of a 3-point line (lat 10..12, lon 20..22): type POINT, 1 term, centroid (0, 0), a 0-size bounding box. The line's own field: LINE, 2 terms, centroid about lat 11, lon 21.XYShapeDocValuesFieldthe same.- The point-range sibling
LongRange, reused the same way, returns 10/20: it decodes its current bytes (LongRange.java:156-170), though from index 0 of the backing array, ignoring theBytesRefoffset (:173-181).
Exposure and likelihood170 words
- Who. Application code that reuses a
Long/Int/Float/DoubleRangeDocValuesFieldorLatLon/XYShapeDocValuesFieldobject throughsetBytesValue(the documented reuse idiom, and the one Lucene's ownTestRangeOnRangeFacetCountsuses on the range classes) and then readsgetMin/getMax,getCentroid,getBoundingBox,numberOfTermsorgetHighestDimensionTypefrom that object. - Default vs opt-in. No setting involved; the trigger is the Expert setter followed by a getter call on the same object.
- Effect. A public getter silently returns a value that is not the field's value. The indexed doc value, the only thing these fields index, is the new range: the independent reproducer's control test and the diagram probe read it back and run one
newSlowIntersectsQuery([15, 15] matches, [1, 2] does not); no shape query was run. Nothing inlucene/*/src/javareads these getters, and no Lucene test reads the range getters at all. - Scale. None: one field, one call.
- Likelihood. Rare: the application usually still holds the values it just encoded, and the recorded searches found no report since the range classes were added in 2019.
One JUnit test method on the public API: build the field for [1, 2], setBytesValue with the bytes of [10, 20], assert getMin(0) is 10 and getMax(0) is 20. No index, single thread, deterministic; it fails with assertions on and off.
/*
* 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.document;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.apache.lucene.util.BytesRef;
public class TestRangeDocValuesFieldGettersAfterSetBytesValue extends LuceneTestCase {
public void testGettersFollowSetBytesValue() {
LongRangeDocValuesField field =
new LongRangeDocValuesField("r", new long[] {1}, new long[] {2});
byte[] encoded = new byte[2 * Long.BYTES];
LongRange.verifyAndEncode(new long[] {10}, new long[] {20}, encoded);
field.setBytesValue(new BytesRef(encoded));
// expected: getMin/getMax report the new range [10, 20]; actual: they still return 1 and 2
assertEquals(10, field.getMin(0));
assertEquals(20, field.getMax(0));
}
}
cp $REPRO/TestRangeDocValuesFieldGettersAfterSetBytesValue.java $LUCENE/lucene/core/src/test/org/apache/lucene/document/
cd $LUCENE && ./gradlew :lucene:core:test --tests org.apache.lucene.document.TestRangeDocValuesFieldGettersAfterSetBytesValue -Ptests.seed=DEADBEEF
# same failure with -Ptests.asserts=false
- Expected
getMin(0)returns 10 andgetMax(0)returns 20, the range the field now indexes.- Observed
getMin(0)returns 1, the constructor's value:java.lang.AssertionError: expected:<10> but was:<1>, with assertions on and off.
More files, environment, full output188 words
TestRangeDocValuesFieldGettersAfterSetBytesValue (one method, extends LuceneTestCase, package org.apache.lucene.document of the lucene/core tests). It carries the Apache license header and passes checkGoogleJavaFormat, and it is also the regression test. It touches no index, so the framework's randomization does not affect it; it failed under every recorded seed: DEADBEEF (gradle, and the hunt's runs), and with the hunt's code-identical earlier copy 5EED3A01, 5EED3A02 (separate re-runs on main) and 5EED1234 (the adjudication, on the 10.6.0 jars). With fix A it passes unchanged.
Before 9.5.0 LongRange.verifyAndEncode was package-private; there an application gets the same bytes from another field's binaryValue(), as the independent reproducer's test does.
| lucene | main 5adff78bb168 (12.0.0-SNAPSHOT): fails; 8.2.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.6.0: fail |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux aarch64 (pure Java; only this platform was run) |
cp $REPRO/TestRangeDocValuesFieldGettersAfterSetBytesValue.java $LUCENE/lucene/core/src/test/org/apache/lucene/document/
cd $LUCENE && ./gradlew :lucene:core:test --tests org.apache.lucene.document.TestRangeDocValuesFieldGettersAfterSetBytesValue -Ptests.seed=DEADBEEF
# gradle, scratch worktree at 5adff78bb168, -Ptests.seed=DEADBEEF
TestRangeDocValuesFieldGettersAfterSetBytesValue > testGettersFollowSetBytesValue FAILED
java.lang.AssertionError: expected:<10> but was:<1>
at __randomizedtesting.SeedInfo.seed([DEADBEEF:8DD656BDABA7D24C]:0)
at org.junit.Assert.fail(Assert.java:89)
at org.junit.Assert.failNotEquals(Assert.java:835)
at org.junit.Assert.assertEquals(Assert.java:647)
at org.junit.Assert.assertEquals(Assert.java:633)
at org.apache.lucene.document.TestRangeDocValuesFieldGettersAfterSetBytesValue.testGettersFollowSetBytesValue(TestRangeDocValuesFieldGettersAfterSetBytesValue.java:31)
# -Ptests.asserts=false: the same failure
# with fix A: PASS, assertions on and offsetBytesValueis documented to "change the value of this field"; the getters to "Get the minimum value for the given dimension". Nothing calls them constructor snapshots.- Lucene's own
TestRangeOnRangeFacetCountsreuses these exact classes throughsetBytesValue. - Siblings stay consistent:
LongRangedecodes its current bytes,KeywordFieldre-syncs in an override,LongPointrejects the setter.
The full argument170 words
- Contract.
Field.setStringValue: "Expert: change the value of this field. This can be used during indexing to re-use a single Field instance" (Field.java:294-303);setBytesValue(BytesRef)refers to it (:329-334). The getters: "Get the minimum value for the given dimension" (LongRangeDocValuesField.java:40), "retrieves the centroid location for the geometry" (ShapeDocValuesField.java:97). - Siblings. The named siblings show three established approaches. Decode the current bytes:
LongRange.getMin/getMax(LongRange.java:156-170). Re-sync in a setter override:KeywordField(KeywordField.java:125-132),StringField(StringField.java:108-115). Reject the setter:LongPoint(LongPoint.java:75-78),IntPoint,FloatPoint,DoublePoint. - Lucene's own usage.
TestRangeOnRangeFacetCounts.java:1543-1561encodes withLongRange/DoubleRange.verifyAndEncode(public since #11901, 9.5.0) and callsfield.setBytesValue(new BytesRef(encodedValue)); it never reads the getters. - History. No commit ever overrode
setBytesValuein these classes (git log -S setBytesValue). In the review of #11901 (2022-12-09/10) a proposed publicLongRangeDocValuesField.setLongRangeValue, which checked and encoded the new range and then changed only the bytes throughsetBytesValue, was dropped as out of scope; nobody mentioned the getters.
The devil's-advocate check188 words
The strongest case for "intended": setBytesValue is an Expert hook for re-using a Field instance to avoid GC cost (Field.java:294-298); it takes raw bytes unchecked (only a non-null BytesRef is required, :336-344, for the range and shape classes alike); and the typed getters can be read as metadata of the constructed value. The shape classes are also @lucene.experimental and meant to be built through the LatLonShape/XYShape factories (one of which takes an existing BytesRef encoding).
Why the bug reading is better supported for the range classes: the setter is documented to change the field's value, the bytes are valid Lucene encodings, Lucene's own tests use this exact idiom on these classes, and the sibling LongRange returns the new value after the same call. A maintainer could still treat raw byte replacement as deliberately low-level and prefer documenting the getters (option C). The cross-model reviews rated the intended argument moderate, and the devil's-advocate verifier found no surviving intended argument for the range classes; for the shape classes the misuse argument is stronger, so they come second. The argument bears on likelihood, hence priority Low.
Recommended Decode getMin/getMax from the current bytes (fix A)
Make getMin/getMax of the four range fields decode the field's current bytes, as LongRange does, with the dimension check taken from those bytes. The repro passes with assertions on and off, and the closest suites, including the facet tests that reuse these fields, pass: 62 classes, 525 tests, 0 failures.
public long getMin(int dimension) {
BytesRef b = binaryValue();
if (dimension < 0 || dimension >= b.length / (2 * LongRange.BYTES)) {
throw new IllegalArgumentException("Dimension out of valid range");
}
return NumericUtils.sortableBytesToLong(b.bytes, b.offset + dimension * LongRange.BYTES);
}
// getMax: the same, at b.offset + b.length / 2 + dimension * LongRange.BYTESAll options and trade-offs4 options ยท 559 words
Option A was built and checked through gradle on 5adff78bb168; B, C and the shape fix were not run. A first run that changed only the decode and kept the old dimension guard gave the same test results; the guard change is needed because the decode would turn today's off-by-one guard into a silent wrong value.
In each of Long/Int/Float/DoubleRangeDocValuesField, read binaryValue(), check the dimension against the dimension count of those bytes, and decode the value with the matching point-range class's encoding (unlike LongRange, honouring the BytesRef offset). The other three classes differ only in the decode call.
public long getMin(int dimension) {
BytesRef b = binaryValue();
if (dimension < 0 || dimension >= b.length / (2 * LongRange.BYTES)) {
throw new IllegalArgumentException("Dimension out of valid range");
}
return NumericUtils.sortableBytesToLong(b.bytes, b.offset + dimension * LongRange.BYTES);
}
// getMax: the same, at b.offset + b.length / 2 + dimension * LongRange.BYTESTrade-offs
The guard must change with the decode: today's dimension > 4 || dimension > min.length (LongRangeDocValuesField.java:42, :51) lets dimension == min.length through, which now ends in an ArrayIndexOutOfBoundsException on min[] but would silently read the first max value once the getter decodes the bytes. Each call then decodes one 4- or 8-byte value (a big-endian read and a few bit operations). The getters also stop following later edits to the caller's own arrays, which the constructor keeps by reference (:36-37). The min/max fields become unused and can be dropped. The patch assumes a well-formed encoding: b.length / (2 * BYTES) is the dimension count only when the length is a multiple of 2 * BYTES, and the setter accepts any bytes, so a malformed length gives a misleading half boundary rather than a clean exception; a b.length % (2 * BYTES) != 0 check would close that (not built). The repro is the regression test.
What the checks validated
- Gradle in a scratch worktree at
5adff78bb168, seedDEADBEEF: the repro fails without the fix (assertions on and off) and passes with it (on and off). - With the fix: the repro, all of
org.apache.lucene.document.*,TestRangeFieldsDocValuesQueryand the facet module'sTestRangeOnRangeFacetCounts(which reuses these fields throughsetBytesValue): 62 classes, 525 tests, 0 failures, 0 errors, 26 skipped by their suites.:lucene:core:checkGoogleJavaFormatpasses. - The repro covers only the Long class. A public-API probe with fix A shadowed in shows Int/Float/Double given the bytes of [10, 20] also return 10/20, 10.0/20.0, 10.0/20.0 (1/2 without the fix), with assertions on and off.
Override setBytesValue in BinaryRangeDocValuesField to throw, as LongPoint does.
Trade-offs
Simple and loud, but it breaks the reuse idiom TestRangeOnRangeFacetCounts relies on and removes the only reuse path these classes have.
A javadoc line on getMin/getMax saying they return the constructor's arguments.
Trade-offs
Smallest change, but the object then reports a value it does not index.
Override setBytesValue in ShapeDocValuesField to re-decode shapeDocValues from the new bytes through a small per-subclass decode hook, or to reject the setter.
Trade-offs
shapeDocValues stops being final; the constructor is package-private, so no outside subclass exists. Fits in the same change or a follow-up.
Lucene itself never reads these getters, so a downstream product is affected only if its own code reuses such a field through setBytesValue and reads the getters back; downstream code was not surveyed.
A proof-obligation audit of the range and shape doc-values fields' getters: a getter documented as the field's minimum must follow every documented way of changing the field's value. setBytesValue changes only the bytes while the getters read the constructor's arrays; a one-method public-API JUnit test then witnessed it.
The method in full122 words
Method: semi-formal (a proof-obligation audit of a class's contract, witnessed by an executable repro).
- The finder read the getters of the range and shape doc-values fields against the obligation that they describe the field's current value, and checked every way the value can change: the only one is the inherited
Field.setBytesValue, which no class in either family overrides. - The finder then wrote a public-API JUnit test that fails on main with assertions on and off and on the 10.6.0 jars, and a probe that shows the same for the other three range classes and both shape classes.
- An independent reproducer, two executed cross-model reviews, a devil's-advocate verifier and an adjudication reproduced it with their own tests ("Prior reports, history and checks").
No public report in the documented searches of apache/lucene issues, PRs and commits (with the JIRA archive), the mailing lists, downstream trackers and the web (2026-11-02). Closest trace: a 2022 review that dropped a proposed range setter built on setBytesValue, without discussing the getters.
Searches3 searches
The recorded searches (2026-11-02) found no public issue, PR, review comment, commit message, JIRA entry, list thread or downstream report that describes these getters going stale after setBytesValue. Closest trace, not a report: in the review of #11901 (2022-12-09/10), gsmiller questioned a proposed public LongRangeDocValuesField.setLongRangeValue as out of scope; it changed only the bytes, through setBytesValue, so it would have had the same staleness; the author (mdmarshmallow) removed it and used setBytesValue directly in TestRangeOnRangeFacetCounts. Related, not the same bug: a sibling finding where the point-range fields ignore a BytesRef offset after setBytesValue (other classes, other fix site). Limits: keyword search; GitHub search matches whole words, so each concrete class name was searched separately; private reports are not visible.
| Where | When | How | Found |
|---|---|---|---|
| GitHub issues, PRs and commits (apache/lucene incl. the migrated JIRA archive) | 2026-11-02, re-checked the same day | two scripted prior-report searches (51 queries, 76 distinct hits after the web results were merged; 19 queries, 9 hits), the finder's 43-query and the devil's-advocate verifier's 17-query runs, and manual searches per concrete class name and for setBytesValue with getter and symptom words; the comment and review threads of #11901, #12053, #12054, #1017, #11690, #9409, #8434, #11974, #1066 and #12049 | no report; #11901's review is the closest trace |
| Open and recent PRs | 2026-11-02 | the changed files of all 408 open apache/lucene PRs and of the 40 most recently created PRs; PRs created since 2026-10-31 | none touches the range or shape doc-values fields or Field.java |
| Mailing lists, JIRA, downstream, web | 2026-11-02 | dev@, java-user@, issues@ and commits@; the JIRA archive; the Elasticsearch, OpenSearch and Solr trackers; 6 web queries | nothing on these getters |
There since the classes were added: LUCENE-8362 (f84afab0083, 2019, first release 8.2.0) for the range fields and LUCENE-10654 (d7fd48c9502, 2022, first release 9.4.0) for the shape fields. Fails on every release run from 8.2.0 to 10.6.0 and on main; not fixed upstream.
Versions tested5 rows
Range fields: f84afab0083 "LUCENE-8362: Introduce DocValues Fields and Range Queries for native Range Field Types" (Atri Sharma, author date 2019-05-27, committed by Adrien Grand 2019-06-10; migrated issue #9409; branch_8x d2ff7ffafcd); first release 8.2.0. Shape fields: d7fd48c9502 "LUCENE-10654: Add new ShapeDocValuesField for LatLonShape and XYShape" (#1017, Nick Knize, 2022-08-09; branch_9x ddf0d0acf4e, #1064); first release 9.4.0.
git blame on main puts the getter lines on these commits (apart from the whitespace-only 2d6ad2fee6d reformat), and git log -S setBytesValue finds no commit on any of the eight field classes: not a regression. Releases between the ones run were not executed; the lines are unchanged. Not fixed upstream: upstream main 1c2072f0284 (2026-11-02) has no newer commit on these files.
| Version or commit | Verdict | Result |
|---|---|---|
main 5adff78bb168 | affected | gradle: fails with assertions on and off; passes with fix A |
| 8.2.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.6.0 | affected | the hunt's code-identical earlier copy of the repro (same statements, other comments): expected:<10> but was:<1> (on 8.x and 9.0.0 with the older test-framework import) |
| 9.4.0, 10.6.0 | shape probe | a point field given a line's bytes: expected:<LINE> but was:<POINT> |
| 8.1.1 | no such class | compile error: LongRangeDocValuesField does not exist yet |
| 9.3.0 | no such class | compile error: LatLonShapeDocValuesField does not exist yet |
A finder, an independent reproducer, two executed cross-model reviews (OpenAI GPT-5.6, intended and refute lenses), a devil's-advocate verifier, an adjudication, an audit and a final repro check reproduced it and agree on the root cause. This doc's gradle runs reproduced it on main and validated fix A.
Checks and verdicts309 words
The cross-model reviews ran on main ea57360912ed, where the eight field classes, Field, BinaryDocValuesField, LongRange, ShapeDocValues and the facet test are byte-identical to 5adff78bb168; IndexingChain.java differs only outside the cited lines (its BINARY doc-values case is the same code). The other checks on main ran on 5adff78bb168; the release runs used each release's own jars.
| Check | Verdict | What it found |
|---|---|---|
| finder | reproduced | Its one-test JUnit fails on main (assertions on and off) and 10.6.0; a probe shows the other three range classes and both shape classes stale too. |
| cross-model review, intended lens (OpenAI GPT-5.6, executed) | real (high) | Own test: the new bytes are indexed while the getters keep the old state; a freshly built field reports the new values; intended case moderate. |
| independent reproducer | real | Own test (bytes from another field's binaryValue()) fails 2 of 3 on main, 10.6.0, 10.5.2 and 9.12.3 (range and shape); its index control passes. |
| cross-model review, refute lens (OpenAI GPT-5.6, executed) | real (high), could not refute | Own two-dimensional test: the bytes reach the doc values, but getMin(0) returns -7 instead of 100; the fresh-field control passes. |
| devil's-advocate verifier | real (medium) | Found no surviving intended argument for the range classes; a moderate misuse case remains for the experimental shape classes. |
| adjudication | confirmed (medium), Low | Re-ran the reproducer's test on main and the repro on the 10.6.0 jars: fail. |
| separate re-run, audit and final repro check | reproduced; confirmed | The repro fails on main with assertions on and off; real but minor, since the indexed value is correct. |
| this doc | reproduced; fix A validated | Gradle: fails without the fix; with it the repro and the closest suites pass (62 classes, 525 tests, 0 failures), and a probe shows all four range classes return the new range. |
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 457 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,498 words
- Registry title
- Long/Int/Float/DoubleRangeDocValuesField getMin/getMax (and LatLon/XYShapeDocValuesField getCentroid, getBoundingBox, numberOfTerms, getHighestDimensionType) keep returning the constructor's value after Field.setBytesValue gives the field a new value, while the index gets the new value
- Area
- core / document: range and shape doc-values fields (Long/Int/Float/DoubleRangeDocValuesField getMin/getMax over BinaryRangeDocValuesField; LatLon/XYShapeDocValuesField getCentroid, getBoundingBox, numberOfTerms, getHighestDimensionType over ShapeDocValuesField; the inherited Field.setBytesValue)
- Priority
- Low, rank 879: After a range or shape doc-values field is reused through setBytesValue, its typed getters keep returning the old value while the index and queries use the new one, in every release since 8.2.0. Nothing is indexed wrongly and only applications that read those getters on the reused object notice, so it is a rare, minor API defect.
- Impact class
- minor
- Impact note
- Only the typed getters of the reused field object are wrong: after Field.setBytesValue, getMin/getMax of a Long/Int/Float/DoubleRangeDocValuesField (and getCentroid, getBoundingBox, numberOfTerms and getHighestDimensionType of a LatLon/XYShapeDocValuesField) still describe the constructor's value, identically with -ea and -da [exec], while the indexed doc value and every query use the new bytes (the independent reproduction's control: the reused document matches the range [15,15] and not [1,2]); nothing in lucene/*/src/java reads these getters, so it hits only applications that reuse such a field instance through setBytesValue (the reuse idiom Lucene's own TestRangeOnRangeFacetCounts applies to these classes) and then read the getters of that same object; no setting and no scale are needed; in every release since 8.2.0 (range) and 9.4.0 (shape).
- Summary
- You reuse a LongRangeDocValuesField the way Field's javadoc suggests: you build it for the range [1, 2] and later call setBytesValue with the encoding of [10, 20], so the field now indexes [10, 20] and you expect getMin(0) to return 10 and getMax(0) to return 20. Instead they still return 1 and 2, because the class answers from the arrays it kept from its constructor and does not override setBytesValue. Int/Float/DoubleRangeDocValuesField behave the same, and LatLon/XYShapeDocValuesField likewise keep the old shape's centroid, bounding box, term count and dimension type.
- Introduced
- f84afab0083f61675498b685b8c26fca9a37c043 ('LUCENE-8362: Introduce DocValues Fields and Range Queries for native Range Field Types', Atri Sharma, committed by Adrien Grand; migrated issue #9409; reviewed as JIRA patches, no GitHub PR; branch_8x copy d2ff7ffafcd) for the four range doc-values fields, and d7fd48c9502c ('LUCENE-10654: Add new ShapeDocValuesField for LatLonShape and XYShape', #1017, Nick Knize; branch_9x copy ddf0d0acf4e, #1064) for the shape doc-values fields [read: git blame on main 5adff78bb168 puts LongRangeDocValuesField.java:27-56 and BinaryRangeDocValuesField.java:22-37 on f84afab0083, apart from whitespace-only lines of the 2d6ad2fee6d reformat (LUCENE-9570, 2020-12-23), and ShapeDocValuesField.java:43, :54-58, :85-87 and :97-107 on d7fd48c9502; git log -S setBytesValue finds no commit on any of the eight field classes, so none ever overrode the inherited setter]. Not a regression: the stale getters are there from the start in both families. (2019-05-27 (author date of f84afab0083; committed 2019-06-10); shape doc-values fields 2022-08-09 (d7fd48c9502 and ddf0d0acf4e))
- First affected version
- 8.2.0 [exec] for the range doc-values fields (the shipped repro fails there with -ea and -da; on 8.1.1 it does not compile, LongRangeDocValuesField is absent); 9.4.0 [exec, shape probe] for the shape doc-values fields (on 9.3.0 the probe does not compile, LatLonShapeDocValuesField is absent)
- Affected versions
- Range doc-values fields: every release from 8.2.0 through 10.6.0 (released 2026-10-29, 4ae946e5a9b) and main 5adff78bb168 (12.0.0-SNAPSHOT); not fixed upstream. [exec] the shipped one-test repro fails with 'expected:<10> but was:<1>' on 8.2.0 (-ea and -da), 8.11.4, 9.0.0, 9.12.3, 10.0.0 and 10.6.0 (-da) and on main (-ea and -da, also in the re-run); the 8.x and 9.0.0 runs use the same test with only the older LuceneTestCase import; the independent reproduction's test fails on main (-ea, -da), 10.6.0, 10.5.2 and 9.12.3. Shape doc-values fields: every release from 9.4.0 on and main; [exec] a shape probe (a point field given a 3-point line's bytes, then getHighestDimensionType) fails with 'expected:<LINE> but was:<POINT>' on 9.4.0 and 10.6.0, and the independent reproduction's shape test fails on main, 10.6.0, 10.5.2 and 9.12.3. [read] the releases in between were not run, but blame shows the lines unchanged since the introducing commits, and upstream main 1c2072f02843 (2026-11-02) has no newer commit on these files. Not new in 10.6. All runs JDK 25.0.3 on Linux aarch64.
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: an independent repro, a cross-model review that agreed, an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
- Found by
- semi-formal: proof-obligation audit
- Upstream and prior reports
- none found (2026-11-02, on top of the finder's 43-query and the intended-behaviour check's 17-query scripted prior-report searches, which agree; rechecked the same day): two scripted prior-report searches (51 queries with 76 distinct hits, and 19 queries with 9 distinct hits) over apache/lucene issues, PRs and commits, the Elasticsearch, OpenSearch and Solr trackers, the dev@, java-user@, issues@ and commits@ lists, the JIRA archive and 6 web queries; manual GitHub searches (each concrete class name separately, because GitHub search matches whole words only; setBytesValue with getter and symptom words; open PRs); the comment and review threads of #11901, #12053, #12054, #1017, #11690, #9409, #8434, #11974, #1066 and #12049; the changed files of all 408 open PRs and of the 40 most recently created PRs; the upstream commit history of every fix-site file: no report of the range or shape doc-values getters going stale after setBytesValue. Closest trace, not a report: in the review of #11901 (2022-12-09/10) gsmiller questioned a proposed public LongRangeDocValuesField.setLongRangeValue setter as out of scope; it only called setBytesValue, so it would have had the same staleness; the author (mdmarshmallow) removed it and used setBytesValue directly in TestRangeOnRangeFacetCounts; nobody mentioned the getters. Same-day recheck: GitHub search on apache/lucene 'setBytesValue' (3: #11596, #12053, #11901), 'LongRangeDocValuesField' (3: #11869, #11901, #841), 'ShapeDocValuesField' (3: #1017, #1064, #11690), '"RangeDocValuesField" getMin' (0), 'LatLonShapeDocValuesField getCentroid' (1: #1017); none of the PRs created since 2026-10-31 (#16956 to #16966) touches these files; last upstream commits: Long/BinaryRangeDocValuesField 2d6ad2fee6d (2020-12-23, reformat), Shape/LatLonShapeDocValuesField d7fd48c9502 (2022-08-09, origin), Field.java 5158a52a082 (2026-04-24, unrelated); upstream main head 1c2072f02843.
- Lucene commit tested
- main
5adff78bb168(2026-11-02) through gradle in a scratch worktree with assertions on and off, without and with fix A (the repro, all oforg.apache.lucene.document.*,TestRangeFieldsDocValuesQuery, the facet module'sTestRangeOnRangeFacetCounts,checkGoogleJavaFormat), and through a standalone JUnit runner on the main jars. The hunt ran the repro on the 8.2.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0 and 10.6.0 jars (fail; 8.1.1 has no such class) and a shape probe on 9.4.0 and 10.6.0 (fail; 9.3.0 has no such class). Only Linux aarch64 with OpenJDK 25.0.3 was run.
- Finding record
lucene/core/src/java/org/apache/lucene/document/LongRangeDocValuesField.java:22-25 (at 5adff78bb168) (class javadoc: 'an extension of BinaryDocValuesField'), :27-29 and :32-38 (final min/max arrays kept from the constructor, by reference), :40-56 (getMin/getMax, javadoc 'Get the minimum/maximum value for the given dimension', read min[]/max[]); IntRangeDocValuesField.java, FloatRangeDocValuesField.java, DoubleRangeDocValuesField.java:28 and :41 (same pattern)lucene/core/src/java/org/apache/lucene/document/BinaryRangeDocValuesField.java:22-37 (at 5adff78bb168) (package-private base class; packedValue and numDims fixed at construction; no setBytesValue override)lucene/core/src/java/org/apache/lucene/document/ShapeDocValuesField.java:38 (at 5adff78bb168) (@lucene.experimental), :43 (protected final ShapeDocValues shapeDocValues), :54-58 (decoded once; fieldsData = shapeDocValues.binaryValue()), :85-87 (numberOfTerms), :97-101 (abstract getCentroid/getBoundingBox), :107-109 (getHighestDimensionType); LatLonShapeDocValuesField.java:28-45 (instantiate through LatLonShape#createDocValueField, including from an existing BytesRef encoding), :61-63 (protected BytesRef constructor), :66-75 (getCentroid/getBoundingBox read shapeDocValues); XYShapeDocValuesField.java:66-74 (same)lucene/core/src/java/org/apache/lucene/document/Field.java:294-303 (at 5adff78bb168) (setStringValue javadoc: 'Expert: change the value of this field. This can be used during indexing to re-use a single Field instance'), :329-346 (setBytesValue(BytesRef) replaces fieldsData whenever it is a BytesRef); KeywordField.java:116-132 and StringField.java:100-115 (overrides that keep derived state in sync); LongPoint.java:75-78 (setBytesValue throws); LongRange.java:134 (public verifyAndEncode), :156-170 (getMin/getMax decode the current fieldsData)lucene/facet/src/test/org/apache/lucene/facet/rangeonrange/TestRangeOnRangeFacetCounts.java:1543-1561 (at 5adff78bb168) (Lucene's own tests reuse Long/DoubleRangeDocValuesField with field.setBytesValue(new BytesRef(encodedValue)); they never read the getters); grep of lucene/*/src/java: no caller of these getters outside the field classeshttps://github.com/apache/lucene/pull/11901 (review comments 1044827355, 1044851717, 1044897144, 1044928215 on LongRangeDocValuesField.java, 2022-12-09/10, and commit 377df3e387 that added and later dropped setLongRangeValue, read 2026-11-02), https://github.com/apache/lucene/pull/12053, https://github.com/apache/lucene/pull/12054, https://github.com/apache/lucene/pull/1017, https://github.com/apache/lucene/issues/11690, https://github.com/apache/lucene/issues/9409
- The finding in 30 seconds
lucene/core/src/java/org/apache/lucene/document/LongRangeDocValuesField.java:27-56; (at 5adff78bb168) IntRangeDocValuesField.java, FloatRangeDocValuesField.java, DoubleRangeDocValuesField.java:28-29, :41-55lucene/core/src/java/org/apache/lucene/document/BinaryRangeDocValuesField.java:22-37 (at 5adff78bb168)lucene/core/src/java/org/apache/lucene/document/Field.java:294-303, (at 5adff78bb168) :329-346, :428-434lucene/core/src/java/org/apache/lucene/index/IndexingChain.java:1741-1742; (at 5adff78bb168) BinaryDocValuesField.java:44; ShapeDocValuesField.java:49 (BINARY doc values only)lucene/core/src/java/org/apache/lucene/document/ShapeDocValuesField.java:43, (at 5adff78bb168) :54-58, :85-87, :97-109; LatLonShapeDocValuesField.java:66-75; XYShapeDocValuesField.java:66-74lucene/core/src/java/org/apache/lucene/document/LongRange.java:156-170, (at 5adff78bb168) :173-181
- Who is affected
lucene/facet/src/test/org/apache/lucene/facet/rangeonrange/TestRangeOnRangeFacetCounts.java:1543-1561 at 5adff78bb168
- Why it looks unintended
lucene/core/src/java/org/apache/lucene/document/Field.java:294-303 (at 5adff78bb168) (Expert, GC cost at :294-298), :329-346 (type and null checks only at :336-344)lucene/core/src/java/org/apache/lucene/document/LongRangeDocValuesField.java:40, (at 5adff78bb168) ShapeDocValuesField.java:38, :97, LatLonShapeDocValuesField.java:28-45lucene/core/src/java/org/apache/lucene/document/LongRange.java:156-170, (at 5adff78bb168) KeywordField.java:116-132, StringField.java:100-115, LongPoint.java:75-78, IntPoint.java:76, FloatPoint.java:100, DoublePoint.java:100lucene/facet/src/test/org/apache/lucene/facet/rangeonrange/TestRangeOnRangeFacetCounts.java:1543-1561 (at 5adff78bb168)https://github.com/apache/lucene/pull/11901 (review comments 2022-12-09/10; the proposed setter in the PR's commit 377df3e387)
- Suggested fix
lucene/core/src/java/org/apache/lucene/document/LongRangeDocValuesField.java:36-37, (at 5adff78bb168) :42, :51; LongPoint.java:75-78; ShapeDocValuesField.java:43, :54-58; lucene/facet/src/test/org/apache/lucene/facet/rangeonrange/TestRangeOnRangeFacetCounts.java:1543-1561
- Prior reports
https://github.com/apache/lucene/pull/11901 (review comments 2022-12-09/10)
- Diagrams
lucene/core/src/java/org/apache/lucene/document/LongRangeDocValuesField.java:27-56, (at 5adff78bb168) Field.java:335-346, lucene/core/src/java/org/apache/lucene/index/IndexingChain.java:1741-1742
- TestRangeDocValuesFieldGettersAfterSetBytesValue.java (lucene/core, package org.apache.lucene.document, 1 test):
repro/LFV-1146/TestRangeDocValuesFieldGettersAfterSetBytesValue.java(34 lines)
The diagram illustrates the repro's calls against main 5adff78bb168 (LongRangeDocValuesField.java:27-56, Field.java:335-346, IndexingChain.java:1741-1742) and the diagram probe's executed values.
assets/LFV-1146/bug.png(457 KB)
![Diagram of the bug: new LongRangeDocValuesField("r", {1}, {2}) holds the bytes of [1, 2] and keeps its min/max arrays {1}/{2}. setBytesValue with the bytes of [10, 20] replaces only the bytes. Indexing reads the bytes, so the doc value is [10, 20] and a query for [15, 15] matches; getMin(0)/getMax(0) read the kept arrays and return 1 and 2 instead of 10 and 20.](../assets/LFV-1146/bug.png)