Rectangle is Lucene's lat/lon box. Its constructor rejects a bad coordinate with IllegalArgumentException, but it checks that minLat <= maxLat only with a Java assert, which the JVM skips unless it was started with -ea.
new Rectangle(minLat, maxLat, minLon, maxLon)is a lat/lon box.minLon > maxLonis allowed and means the box crosses the dateline; no inversion of latitude is documented.LatLonShape.newBoxQueryandnewSlowDocValuesBoxQuerybuild aRectanglefrom their four arguments, and so doesSimpleWKTShapeParserfor the textENVELOPE(minLon, maxLon, maxLat, minLat).LatLonPoint.newBoxQuerybuilds none.- A Java
assertruns only when the JVM is started with-ea; a JVM started without the flag skips every assert. Lucene's gradle test runs enable assertions for about 75% of seeds by default (tests.asserts, since #15325).
645d4fe14a20: the box passed with its latitudes swapped, what the constructor does with -ea and -da, which polygons the shape query returns, and the one-line fix. Select the still to open it at full size. Generated with AI, checked against the code.- Index four polygons, at latitudes 0..20, 6..8, 12..15 and 1..3. Query the box between latitudes 5 and 10, but pass the two latitudes swapped:
LatLonShape.newBoxQuery("shape", INTERSECTS, 10, 5, 0, 1), that is minLatitude 10 and maxLatitude 5. - You expect
IllegalArgumentException: whatnew XYRectangle(0, 1, 10, 5)gives ("minY must be lower than maxY, got 10.0 > 5.0"), and whatRectangleitself gives for latitude 95. - With assertions on (
-ea; Lucene's gradle tests enable them for about 75% of seeds) you get a bareAssertionError, no message, fromRectangle.<init>. The same fornew Rectangle(10, 5, 0, 1)and for the WKT textENVELOPE(0, 1, 5, 10). - With assertions off (the JVM default) the box is accepted. INTERSECTS returns 1 polygon, lat 0..20, the only one that crosses both latitude lines. The correctly ordered box returns 2 (lat 6..8 and 0..20), and
LatLonPoint.newBoxQuerywith the same swapped box returns 0 hits. - Why: the
Rectangleconstructor rejects an out-of-range coordinate withIllegalArgumentException, but checks the latitude order only withassert maxLat >= minLat. It has done so since the class first shipped, in 5.4.0 (2015).
Callers of the core lat/lon box APIs (new Rectangle, LatLonShape.newBoxQuery, newGeometryQuery with a Rectangle, WKT ENVELOPE) that pass swapped latitudes, for example user-entered corners forwarded unchecked. Default settings, every release since 5.4.0. Valid boxes are not affected and nothing is stored. Rare: the recorded searches found no public report since 2015.
Mechanism, terms and numbers533 words
org.apache.lucene.geo.Rectangle(minLat, maxLat, minLon, maxLon) range-checks each coordinate with GeoUtils.checkLatitude / checkLongitude, which throw IllegalArgumentException, but checks the latitude order only with assert maxLat >= minLat (Rectangle.java:94 on main 645d4fe14a20). A box with swapped latitudes, built directly, through LatLonShape.newBoxQuery or newSlowDocValuesBoxQuery, for newGeometryQuery, or from a WKT ENVELOPE(minLon, maxLon, maxLat, minLat), therefore throws a bare AssertionError with assertions on and is accepted with assertions off. A LatLonShape box query then returns hits that match no region: neither the empty result that LatLonPoint.newBoxQuery gives for the same box nor the result of the correctly ordered box. The Cartesian twin XYRectangle had the same assert until LUCENE-9218 (8.5.0) made it throw IllegalArgumentException; Rectangle was not changed. The input is invalid, so this is missing validation, not a wrong answer to a valid query. Every release from 5.4.0 to 10.6.0 is affected; a one-line check in the constructor fixes it.
Rectangle(org.apache.lucene.geo): "A rectangle is defined by its minimum and maximum latitude/longitude coordinates. It can cross the dateline, in which case minLon > maxLon" (Rectangle.java:38-39). The fields are "Minimum latitude" and "Maximum latitude" (:67-71).XYRectangle: the same box on a flat (Cartesian) plane, used byXYShape.-ea/-da: JVM flags that turn Javaassertstatements on or off; off is the default.
- The constructor calls
GeoUtils.checkLatitude(minLat),checkLatitude(maxLat),checkLongitude(minLon)andcheckLongitude(maxLon)(Rectangle.java:86-89); each throwsIllegalArgumentExceptionfor an out-of-range value. The only check of the order isassert maxLat >= minLat;(:94). LatLonShape.newBoxQuery(LatLonShape.java:258) andnewSlowDocValuesBoxQuery(:283) pass the caller's four values straight into the constructor;SimpleWKTShapeParser.parseBBoxreadsENVELOPE(minLon, maxLon, maxLat, minLat)and does the same (SimpleWKTShapeParser.java:252-264).- With
-eathat assert throws a barejava.lang.AssertionErrorwith no message. With-dathe boxRectangle(lat=10.0 TO 5.0 lon=0.0 TO 1.0)is kept as is. - The shape query encodes it unchanged (
LatLonShapeBoundingBoxQuery.java:240-246: min latitude 10, max latitude 5). Its point testy < minY || y > maxY(SpatialQuery.java:824-827) is then true for every latitude, so no vertex is ever inside, and its triangle test drops every triangle that does not reach both above 10 and below 5 (:864-869). INTERSECTS keeps only shapes that cross both latitude lines; DISJOINT returns the rest. LatLonPoint.newBoxQuerybuilds noRectangle: it encodes the range from latitude 10 up to 5, which is empty, so it returns 0 hits with-eaand-da(LatLonPoint.java:243-244).XYRectanglechecks the same order withif (minY > maxY) throw new IllegalArgumentException("minY must be lower than maxY, got ...")(XYRectangle.java:41-44), and the same for X.
Polygons: big lat 0..20 (lon -1..2); band 6..8, north 12..15, south 1..3 (lon 0.2..0.8). Box lon 0..1, assertions off:
| query | box lat 10..5 (swapped) | box lat 5..10 |
|---|---|---|
LatLonShape.newBoxQuery, INTERSECTS | [big] | [band, big] |
| WITHIN | [] | [band] |
| DISJOINT | [band, north, south] | [north, south] |
| CONTAINS | [big] | [big] |
LatLonShape.newSlowDocValuesBoxQuery, INTERSECTS | [big] | [band, big] |
LatLonPoint.newBoxQuery (a point at lat 7) | [] | [point] |
With assertions on, every LatLonShape row and LatLonPoint.newGeometryQuery(field, new Rectangle(10, 5, 0, 1)) throw AssertionError; the LatLonPoint.newBoxQuery row is the same.
Exposure and likelihood256 words
- Who is hit. Any caller of
new Rectangle,LatLonShape.newBoxQuery/newSlowDocValuesBoxQuery,LatLonShapeorLatLonPoint.newGeometryQuerywith aRectangle, orSimpleWKTShapeParser.parseof anENVELOPE, that passesminLat > maxLat. Possible sources: box corners from a user or a map that the application forwards without ordering them, or WKT text, whoseENVELOPE(minLon, maxLon, maxLat, minLat)puts the two latitudes the other way round from the constructor'sminLat, maxLat. How often this happens was not measured. - Default or opt-in. Default: no setting, any codec, any index.
-dais the JVM default. Lucene's gradle test runs enable assertions for about 75% of seeds by default (tests.asserts, randomized since #15325,TestsAndRandomizationPlugin.java:240-249). - Effect with
-da. No error and a hit set with no meaning: not empty (what the point queries return) and not the correctly ordered box. A bug in the caller's own box code goes unnoticed. - Effect with
-ea. A bareAssertionErrorwith no message. It is anError, socatch (IllegalArgumentException e)andcatch (Exception e)do not see it. - Scale. One invalid query at a time. Valid boxes are not affected, and nothing is written to the index.
- How likely: rare. Two public records show a caller's own computation building such a box, both caught by the assert in tests: a CI failure posted on LUCENE-9552 (#10591), inside Lucene (quantization of a very thin box), and elastic/elasticsearch#32857 (an overflow in that project's geohash code). The recorded searches found no public report of the missing check itself since 5.4.0.
One JUnit test for lucene/core, public API only, no index: build new Rectangle(10, 5, 0, 1) (minLat 10 above maxLat 5) and expect IllegalArgumentException. Run it with assertions on and off; it fails both ways.
/*
* 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.geo;
import org.apache.lucene.tests.util.LuceneTestCase;
public class TestRectangleInvertedLatitude extends LuceneTestCase {
public void testInvertedLatitudeRejected() {
// expected: IllegalArgumentException; actual: AssertionError with -ea, no exception with -da
expectThrows(IllegalArgumentException.class, () -> new Rectangle(10, 5, 0, 1));
}
}
cp $REPRO/TestRectangleInvertedLatitude.java $LUCENE/lucene/core/src/test/org/apache/lucene/geo/
cd $LUCENE && ./gradlew :lucene:core:test --tests TestRectangleInvertedLatitude -Ptests.seed=DEADBEEF -Ptests.asserts=true
# and the same with -Ptests.asserts=false
- Expected
IllegalArgumentException, asXYRectanglegives for minY > maxY andRectanglegives for latitude 95.- Observed
- With
-eaa bareAssertionErroratRectangle.<init>(Rectangle.java:94); with-dano exception, the box is accepted.
More files, environment, full output191 words
TestRectangleInvertedLatitude (27 lines with the license header, package org.apache.lucene.geo): one test method, one constructor call, single thread, deterministic (the seed does not matter). It is also the suggested regression test, or its method can go into an existing geo test class (core has no TestRectangle).
It fails on main 645d4fe14a20 through gradle and through a standalone runner, with assertions on and off, and passes with the fix. The shape-query hit lists (above) are what callers get today; they are not part of the test.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT); the same outcome on the 10.6.0, 10.5.2 and 9.12.3 release jars |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux, aarch64 (the only platform run; the code path is plain Java argument checking) |
cp $REPRO/TestRectangleInvertedLatitude.java $LUCENE/lucene/core/src/test/org/apache/lucene/geo/
cd $LUCENE && ./gradlew :lucene:core:test --tests TestRectangleInvertedLatitude -Ptests.seed=DEADBEEF -Ptests.asserts=true
# and the same with -Ptests.asserts=false
# gradle, -Ptests.asserts=true
TestRectangleInvertedLatitude > testInvertedLatitudeRejected FAILED
java.lang.AssertionError
at __randomizedtesting.SeedInfo.seed([DEADBEEF:786E43E21F277DB3]:0)
at org.apache.lucene.geo.Rectangle.<init>(Rectangle.java:94)
at org.apache.lucene.geo.TestRectangleInvertedLatitude.lambda$testInvertedLatitudeRejected$0(TestRectangleInvertedLatitude.java:25)
...
1 test completed, 1 failed
# gradle, -Ptests.asserts=false
TestRectangleInvertedLatitude > testInvertedLatitudeRejected FAILED
junit.framework.AssertionFailedError: Expected exception IllegalArgumentException but no exception was thrown
at __randomizedtesting.SeedInfo.seed([DEADBEEF:786E43E21F277DB3]:0)
...
at app//org.apache.lucene.geo.TestRectangleInvertedLatitude.testInvertedLatitudeRejected(TestRectangleInvertedLatitude.java:25)
1 test completed, 1 failedXYRectanglehad the sameassertuntil LUCENE-9218 (8.5.0) replaced it withIllegalArgumentException("minY must be lower than maxY ...");Rectanglewas not changed.- The constructor already throws
IllegalArgumentExceptionfor every out-of-range coordinate, and spatial3dGeoRectangleand sandboxLatLonBoundingBoxrejectminLat > maxLat. - No test or Lucene caller uses an inverted latitude, and the
-dahits match no reading of the box.
The full argument313 words
- The twin was changed on purpose.
XYRectangleshipped in 8.2.0 withassert minX <= maxX; assert minY <= maxY;(sandbox, through 8.4.1). LUCENE-9218 (#10258, apache/lucene-solr#1252, Ignacio Vera,4a54ffb553aon main andca3319cdbceon branch_8x, in 8.5.0) replaced both withIllegalArgumentException. Neither the issue nor the PR mentionsRectangle.TestXYRectangle.java:36documents the rule as "maxY must be gte minY" (in a method whose name does not start withtest, so it never runs: LFV-1013). - Same class, other checks.
RectanglethrowsIllegalArgumentExceptionfor latitude 95 ("invalid latitude 95.0; must be between -90.0 and 90.0",GeoUtils.java:87). - Siblings. spatial3d
GeoRectanglethrows "Top latitude less than bottom latitude" (GeoRectangle.java:111-112); sandboxLatLonBoundingBoxthrows "cannot have minLat [..] exceed maxLat [..]" (LatLonBoundingBox.java:124-128);PolygonandLinevalidate their arguments withIllegalArgumentException(Polygon.java:66-90,Line.java:55-64). - Tests and callers. No upstream test passes an inverted latitude; the test framework's random boxes swap the latitudes to avoid one (
GeoTestUtil.java:495-499). The Lucene callers reviewed build ordered boxes, andGeoEncodingUtils.createSubBoxesreturns an empty grid for a box that quantization inverts before it builds anyRectangle(GeoEncodingUtils.java:237-240; the guard dates from LUCENE-7661, 2017, and has run on the four doubles, before anyRectangle, since LUCENE-9641, 2021). - No meaning. With
-dathe shape hits are neither empty (the point paths), nor those of the ordered box, nor a pole-crossing reading (which would match the lat 12..15 and 1..3 polygons). - Direction. #16919 (merged 2026-10-28) turned other
AssertionErrors on reachable bad input into regular exceptions; it touched no geo file. - Not a promise in the javadoc. "Constructs a bounding box by first validating the provided latitude and longitude coordinates" (
Rectangle.java:82-84) was added in 2016 (89db4950b9e2, LUCENE-7015), when the constructor already had its four range checks and only theassertfor the order; it does not say the order is checked.
The devil's-advocate check205 words
The case (the read-only cross-model refute review, OpenAI GPT-5.6, and the intended-behaviour devil's advocate). minLat > maxLat is outside the documented domain: only minLon > maxLon is allowed, for the dateline. The assert states a programmer precondition, and behaviour after a broken precondition is unspecified. LatLonShape.newBoxQuery documents no @throws. LatLonPoint.newBoxQuery documents @throws IllegalArgumentException for "invalid coordinates" (LatLonPoint.java:218) and still returns an empty result for this box, so "empty" could be called the de facto answer. In 2015 only computed boxes reached GeoRect; user-supplied bounds came later (the shape box query builds a Rectangle since 7.7.0, LUCENE-8605). SimpleWKTShapeParser came in as a simple utility for tests (LUCENE-8538).
Why it fails. It shows the input is invalid, which the report concedes; it does not show that an AssertionError or silent acceptance is the right answer to invalid input on a public API. The maintainers chose IllegalArgumentException for the identical assert in the twin, the constructor throws it for every other bad coordinate, and the -da hits match neither the empty answer of the point paths nor any other reading. The refute reviewer itself called the exception "reasonable API hardening" and the split "a real defensive-validation inconsistency". Expect it to be triaged as hardening.
Recommended Throw IllegalArgumentException in the Rectangle constructor
Throw IllegalArgumentException in the Rectangle constructor when minLat > maxLat and drop the assert (one file, +4/-1), as XYRectangle does. Validated on main: the repro passes with assertions on and off, and so do the geo and lat/lon test suites. Side effect: LatLonPoint.newGeometryQuery (INTERSECTS) with such a box now throws instead of matching nothing.
GeoUtils.checkLongitude(maxLon);
if (minLat > maxLat) {
throw new IllegalArgumentException(
"minLat must be lower than or equal to maxLat, got " + minLat + " > " + maxLat);
}
this.minLon = minLon;
this.maxLon = maxLon;
this.minLat = minLat;
this.maxLat = maxLat;
// removed: assert maxLat >= minLat;All options and trade-offs4 options ยท 530 words
One fix was built and validated; three alternatives were not built.
In Rectangle(double, double, double, double), after the four range checks (1 file, +4/-1). The finder's fix; the same check XYRectangle has had since 8.5.0.
GeoUtils.checkLongitude(maxLon);
if (minLat > maxLat) {
throw new IllegalArgumentException(
"minLat must be lower than or equal to maxLat, got " + minLat + " > " + maxLat);
}
this.minLon = minLon;
this.maxLon = maxLon;
this.minLat = minLat;
this.maxLat = maxLat;
// removed: assert maxLat >= minLat;Trade-offs
- Fixes every path at once: the constructor, both
LatLonShapebox factories,newGeometryQuerywith aRectangleand the WKTENVELOPE. MatchesXYRectangle, the constructor's own range checks and the sibling boxes. - Behaviour change:
LatLonPoint.newGeometryQuery(field, INTERSECTS, new Rectangle(10, 5, ...))now throws where-dagives 0 hits today (the intended-behaviour check's probe; other relations not run); with-eait already threwAssertionError. LatLonPoint.newBoxQueryandLatLonDocValuesField.newSlowBoxQuerykeep returning 0 hits for the same box, because they never build aRectangle; making them throw too would change an existing public call and is not proposed.- No internal caller was found that would start throwing: the Lucene callers reviewed build ordered boxes, the quantized case of LUCENE-9552 returns an empty grid before any
Rectangleis built (GeoEncodingUtils.java:237-240), and the 351 tests pass. That covers the reviewed paths, not every caller. - Cost: one double comparison next to the four range checks the constructor already runs (also on the shape doc-values path,
LatLonShapeDocValues.computeBoundingBox); not benchmarked. - A CHANGES entry would be usual; none is drafted.
What the checks validated
- Scratch worktree of
645d4fe14a20, gradle, OpenJDK 25.0.3. Stock + the repro: 1 test, 1 failed, with-Ptests.asserts=true(AssertionErroratRectangle.java:94) andfalse(no exception). - Fix + the repro: the repro, the 17 upstream classes of the
org.apache.lucene.geotest package and the 19 classesorg.apache.lucene.document.TestLatLon*plusTestShapeDocValues: 38 suites, 351 tests, 0 failed, 22 skipped (20@Nightly, 1@Ignore, 1@AwaitsFix), with-Ptests.asserts=true(seedDEADBEEF) andfalse(seed5EED1014). :lucene:core:check -x test(google-java-format, ecjLint, forbiddenApis) passes.- Earlier: the finder's 86 upstream geo tests pass with the fix; the adjudication ran the independent reproduction's 5-test class with the fix: 5 of 5 pass (
-da).
Return a no-match query from LatLonShape.newBoxQuery and newSlowDocValuesBoxQuery when minLatitude > maxLatitude, as the point path effectively does and as LUCENE-9552 chose for quantized boxes. Not built.
Trade-offs
Consistent with the point queries, but new Rectangle(10, 5, ...), the WKT ENVELOPE and newGeometryQuery still accept the box silently with -da or throw AssertionError with -ea, and the relations need care: DISJOINT with an empty box should match every shape, not none.
Throw in LatLonShape.newBoxQuery, newSlowDocValuesBoxQuery and SimpleWKTShapeParser.parseBBox. Not built.
Trade-offs
Leaves new Rectangle itself assert-only, so direct users and newGeometryQuery are not covered.
Add an @throws or a note to the constructor without changing behaviour. Not built.
Trade-offs
Assertions-off callers still get silent acceptance and meaningless hits.
Applications that forward user-entered box corners or WKT envelopes to these APIs should order or check the latitudes themselves until a fix ships; with assertions off nothing else catches the swap.
A scan of public and protected methods whose only check of an argument is an assert (430 rows on main) flagged the Rectangle constructor. Running it next to its validating twin XYRectangle and the point query LatLonPoint.newBoxQuery on the stock jars showed the AssertionError and the silent -da acceptance; the WKT path came from the constructor's callers.
The method in full121 words
Method: non-formal (a source-level static scan plus differential testing, no model or bounded-exhaustive check). The hunt's hunt unit for assert-only input checks, a follow-up to #16919, ran on main 645d4fe14a20 on 2026-10-29.
- Scan. A source scanner listed public and protected methods whose only check of a parameter is an
assert: 430 rows. - Differential run. On the stock jars,
RectangleagainstXYRectangle/XYShape.newBoxQueryand againstLatLonPoint.newBoxQuery: anIllegalArgumentExceptionon the Cartesian side, a bareAssertionError(-ea) or silent acceptance (-da) on the lat/lon side, 0 hits on the point side. - Callers. Grepping the constructor's callers added
newSlowDocValuesBoxQueryand the WKTENVELOPEpath. - Reduction. The finder's 3-test JUnit was reduced to the one-line constructor test above.
No prior report found (searched 2026-10-29): GitHub issues and PRs incl. the JIRA archive, commits, downstream trackers, four mailing lists, the web, and the files of all 415 open PRs. Closest: a CI failure on LUCENE-9552 (#10591), where quantization inside Lucene built such a box and the caller was fixed.
Searches3 searches
Related, not reports of this bug (other fix sites): LUCENE-9552 (#10591, fixed by apache/lucene-solr#2131, 2020): a CI failure posted on that issue shows the assert firing when quantization turned a valid thin box into minLat > maxLat; the two point-in-geometry queries of that time were fixed to return nothing (LUCENE-9641 removed both later), the constructor was not changed. elastic/elasticsearch#32857 (2018, fixed there by #32891): that project's geohash code built a swapped-latitude Rectangle and the assert fired in its tests. LUCENE-9217 / LUCENE-9218 (#10257, #10258, apache/lucene-solr#1252, 2020): gave XYRectangle the exception, Rectangle not mentioned. LUCENE-8850 (#9893, open): the analogous request for Polygon. #16919 (merged 2026-10-28): other AssertionError conversions, no geo file. Gaps: the GitHub code searches for the quoted exception messages returned nothing useful, so they add little; private reports are not visible; "no report" holds for the recorded searches only.
| Where | When | How | Found |
|---|---|---|---|
| GitHub issues and PRs (incl. the migrated JIRA archive), commits, downstream trackers | 2026-10-29 | two runs of a scripted prior-report search (32 issue/PR, 6 commit, 4 downstream queries); 37 own searches over apache/lucene, elastic/elasticsearch, opensearch-project/OpenSearch, apache/solr and all of GitHub for the stack frame org.apache.lucene.geo.Rectangle.<init> | no report; the related items above |
| Open and recent PRs | 2026-10-29 | the changed files of all 415 open apache/lucene PRs; every issue and PR created since 2026-10-26 (#16907-#16938) | none touches Rectangle.java, LatLonShape.java, SimpleWKTShapeParser.java or XYRectangle.java |
| Mailing lists, JIRA, web | 2026-10-29 | 40 queries over dev@, java-user@, issues@ and commits@; 8 JIRA and 6 web queries | no report |
Assert-only since the class was created (LUCENE-6780, dad52a63ce07, 2015), first released in 5.4.0; not a regression. All 100 releases from 5.4.0 to 10.5.2 fail with -ea and -da (history probe), and so does 10.6.0, released 2026-10-29 (the one-test repro). XYRectangle got the exception in 8.5.0; Rectangle did not.
Versions tested6 rows
Introduced by dad52a63ce07, "LUCENE-6780: move GeoBBox -> GeoRect; turn off BKD verbosity" (Michael McCandless, 2015-09-18, on the LUCENE-6780 feature branch), which added assert maxLat >= minLat; as the only order check. It landed on trunk as d369057766ae + 91e6bef3c752 (Nick Knize, 2015-10-21; git blame of Rectangle.java:94 on main points to 91e6bef3c752) and on branch_5x as 09f89f60deb3 (2015-10-22). No PR (Subversion era); LUCENE-6780 (#7838) never discusses the check. The constructor body is unchanged since 2016.
Lineage: sandbox org.apache.lucene.util.GeoRect (5.4.0), lucene-spatial (5.5.0), core org.apache.lucene.geo.Rectangle since 6.1.0 (LUCENE-7163). By path: LatLonShape.newBoxQuery since 7.5.0 in sandbox (7.5.0-7.6.0 built no Rectangle, so even -ea accepted the box; since 7.7.0, LUCENE-8605, -ea throws AssertionError); SimpleWKTShapeParser ENVELOPE since 7.6.0; both in core since 8.5.0.
| Version or commit | Verdict | Result |
|---|---|---|
10.6.0 (released 2026-10-29), main 645d4fe14a20, branch_10x 657014b3c579 (before its decommission) | affected | -ea: bare AssertionError at Rectangle.java:94; -da: box accepted, shape hits match no region |
| 8.5.0 to 10.5.2 (incl. 9.12.3, 10.5.2 with JUnit) | affected | same (on 9.12.3 the assert is at Rectangle.java:61); XYRectangle throws IllegalArgumentException |
| 7.7.0 to 8.4.1 | affected | Rectangle, shape box and WKT: AssertionError (-ea), accepted (-da); XYRectangle (8.2.0+) assert-only too |
| 5.4.0 to 7.6.0 | affected | GeoRect / Rectangle: AssertionError (-ea), accepted (-da); 7.5.0-7.6.0 shape box accepted even with -ea |
| 5.3.2 and older | API absent | no public 2D lat/lon rectangle class (spatial3d's separate GeoRectangle aside) |
| main + fix | not affected | IllegalArgumentException with -ea and -da; geo and lat/lon suites pass |
An independent reproducer, an intended-behaviour devil's advocate and a reading-only review by another model family (OpenAI GPT-5.6) say real; a second reading-only review of that family argued not real but agreed on the root cause. The adjudication re-ran the independent test and confirmed it; this doc re-ran the repro through gradle and validated the fix.
Checks and verdicts411 words
The cross-model reviews were reading-only: they could not run Java, so separate runs re-ran the repro (3 of 3 fail with -da each time). An executing cross-model review is still open.
| Check | Verdict | What it found |
|---|---|---|
| finder | candidate | JUnit (constructor, WKT ENVELOPE, LatLonShape.newBoxQuery for four relations) fails 3 of 3 on main with -ea and -da; also on branch_10x (657014b3c579), 10.5.2, 9.12.3 and the 10.6.0 RC1 jar (the commit released as 10.6.0); the fix passes and 86 upstream geo tests pass. |
| cross-model review, refute lens | not real, medium | OpenAI GPT-5.6, reading-only: the input is outside the documented domain and the assert states a precondition; agreed on the root cause and called the exception "reasonable API hardening". |
| Independent reproducer | real | Own 5-test JUnit with 2 controls: 3 of 5 fail on main (-ea, -da), 10.5.2 and 9.12.3; the controls (XYRectangle, latitude 95) pass. |
| Cross-model review, intended lens | real, medium | OpenAI GPT-5.6, reading-only: works-as-designed case moderate; root cause agreed; fix judged correct from the source. |
| Intended-behaviour devil's advocate | real, medium (Low) | Built the "misuse of the documented domain" case and rejected it; its probe showed the point paths return 0 hits and both shape paths 1. |
| adjudication | confirmed, medium | Re-ran the independent reproduction's test with seed 5EED1234: 3 of 5 fail with -ea and -da, controls pass; with the fix 5 of 5 pass. Wrong error, Low, security none. |
| credibility audit | keep | Re-ran the one-test repro (seed A0D17004) and the intended-behaviour check's probe on main. |
| this doc | confirmed on 645d4fe14a20 | The one-test repro fails through gradle with assertions on and off; the fix validated with gradle. |
| fact check | real; five slips, corrected | Re-ran the doc's gradle command and the fix on 645d4fe14a20 and the one-test repro on the 10.6.0, 10.5.2 and 9.12.3 release jars (-ea and -da); found five factual slips (two counts, the test-assertion default, one commit attribution, the version range), all corrected. |
| cross-model fact check | behaviour real; 4 errors, corrected | OpenAI GPT-5.6, reading only: behaviour real, intended-behaviour case moderate, no-noise call "claim-low"; its 4 errors, 5 overclaims and 3 missing caveats were applied. |
| diagram check | one title wording error, corrected | Checked every on-screen value against the code and the diagram's probe; the summary card's title said the box "is never rejected", which is false with -ea. Re-worded and re-rendered. |
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 518 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,182 words
- Registry title
- geo.Rectangle checks minLat <= maxLat only with an assert, so a lat/lon box with swapped latitudes (new Rectangle, LatLonShape.newBoxQuery, a WKT ENVELOPE) throws a bare AssertionError with -ea and is silently accepted with -da, where LatLonShape box queries then return hits that match no region; the Cartesian twin XYRectangle throws IllegalArgumentException for the same mistake
- Area
- core / geo Rectangle (LatLonShape box queries, newGeometryQuery(Rectangle), SimpleWKTShapeParser ENVELOPE)
- Priority
- Low, rank 416: With assertions off, core LatLonShape box and WKT ENVELOPE queries silently accept swapped latitudes and return meaningless hits only when the caller passes an invalid box.
- Impact class
- wrong error
- Impact note
- Any caller of the public lat/lon box APIs (new Rectangle, LatLonShape.newBoxQuery and newSlowDocValuesBoxQuery, LatLonShape or LatLonPoint newGeometryQuery with a Rectangle, SimpleWKTShapeParser ENVELOPE) that passes swapped latitudes, with the default configuration, in every release since 5.4.0: an IllegalArgumentException is due, but with -ea they get a bare AssertionError and with -da (the production default) the invalid box is accepted and shape queries return hits that match no region; valid boxes are not affected and nothing is persisted.
- Summary
- You build a lat/lon box with the minimum and maximum latitude swapped, for example LatLonShape.newBoxQuery(field, INTERSECTS, 10, 5, 0, 1) or the WKT text ENVELOPE(0, 1, 5, 10), and expect an IllegalArgumentException, as XYRectangle gives for the same mistake and as Rectangle itself gives for an out-of-range latitude. Rectangle's constructor checks the order only with 'assert maxLat >= minLat', so with assertions enabled you get a bare AssertionError, and with assertions disabled the inverted box is accepted. The shape query then matches only shapes that cross both latitude lines (INTERSECTS) or the complement of those (DISJOINT), which is neither the empty result that LatLonPoint.newBoxQuery gives for the same box nor the result of the swapped box.
- Introduced
- dad52a63ce07 (svn r1703879 on the LUCENE-6780 feature branch, by Michael McCandless: 'LUCENE-6780: move GeoBBox -> GeoRect'), which added 'assert maxLat >= minLat' as the only latitude-order check; it reached releases through the merges 09f89f60deb3 (branch_5x, by Nick Knize, 5.4.0) and d369057766ae + 91e6bef3c752 (trunk, by Nick Knize, 6.0.0); git blame of Rectangle.java:94 on main 645d4fe14a20 points to 91e6bef3c752; the constructor body is unchanged since 2016 [read, history clone]. No PR (Subversion era); JIRA LUCENE-6780 (apache/lucene#7838) never discusses the check (2015-09-18 (authored on the feature branch); landed 2015-10-21 on trunk and 2015-10-22 on branch_5x)
- First affected version
- 5.4.0, as org.apache.lucene.util.GeoRect in sandbox, already assert-only [exec: history probe -ea AssertionError, -da accepted]; first non-sandbox release 5.5.0 (lucene-spatial); the current core class org.apache.lucene.geo.Rectangle since 6.1.0 (LUCENE-7163), assert carried over unchanged [exec]. 5.3.2 and older have no public lat/lon rectangle class (API absent) [exec]
- Affected versions
- main 645d4fe14a20 [exec: the one-test minimal repro and the finder, the independent reproduction, the intended-behaviour check and judge JUnits fail with -ea and -da]; branch_10x 657014b3c579 [exec, -da]; 10.6.0 RC1 4ae946e5a9b [exec: main() variant on its core jar, -da]; all 100 releases from 5.4.0 to 10.5.2 on Maven Central [exec: history probe with -ea and -da]; 10.5.2 and 9.12.3 also [exec: the independent reproduction JUnit, -ea and -da]. Upstream main, branch_10x and branch_10_6 heads still have the assert [read, 2026-10-29]. Trigger paths by version: new GeoRect/Rectangle since 5.4.0; LatLonShape.newBoxQuery since 7.5.0 in sandbox (7.5.0-7.6.0 did not build a Rectangle, so even -ea accepted the box; builds one since 7.7.0, LUCENE-8605; core since 8.5.0); SimpleWKTShapeParser ENVELOPE since 7.6.0 in sandbox (core since 8.5.0). XYRectangle had the same assert-only check in 8.2.0-8.4.1 and throws IllegalArgumentException since 8.5.0 (LUCENE-9218). Not affected: 5.3.2 and older (API absent) [exec]
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: an independent repro, a cross-model review that disputed it (the adjudication ruled it real), an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
- Found by
- non-formal
- Upstream and prior reports
- none found (2026-10-29, read-only, the prior-report record verdict new: two scripted prior-report searches with 32 GitHub issue/PR queries incl. the migrated JIRA archive, 6 commit, 4 downstream, 40 dev@/java-user@/issues@/commits@ list, 8 JIRA and 6 web queries; 37 manual GitHub searches over apache/lucene, elastic/elasticsearch, opensearch-project/OpenSearch, apache/solr and GitHub-wide for the stack frame org.apache.lucene.geo.Rectangle.<init>; the changed-file lists of all 415 open apache/lucene PRs; every issue and PR created since 2026-10-26, #16907-#16938). Related items, none the same bug: LUCENE-9552 (#10591, fixed by apache/lucene-solr#2131, 2020): the same assert fired in CI when quantization turned a valid thin box into minLat > maxLat inside an internal caller, fixed at the callers (empty result), constructor unchanged; elastic/elasticsearch#32857 (2018, fixed by elastic/elasticsearch#32891): an overflow in that project's own geohash bounding-box code built a swapped-latitude Rectangle and the assert fired in its tests, fixed in that project; LUCENE-9217/LUCENE-9218 (#10257, #10258, apache/lucene-solr#1252, 2020): gave XYRectangle the IllegalArgumentException, geo.Rectangle not mentioned; LUCENE-8850 (#9893, open): the analogous request for Polygon; #16919 (merged 2026-10-28): other AssertionError conversions, no geo file. Follow-up re-check 2026-10-29: no issue or PR matches 'Rectangle minLat maxLat', 'Rectangle latitude IllegalArgumentException', 'inverted latitude box' or 'newBoxQuery latitude'; no PR created since 2026-10-28 (#16926-#16938) touches a geo file; the only open PR matching 'Rectangle' is #12162 (unrelated); upstream main HEAD is still 645d4fe14a20 (author date 2026-10-28). Not fixed upstream.
- Lucene commit tested
- main
645d4fe14a20(11.0.0-SNAPSHOT): the one-test repro through gradle and a standalone runner, with assertions on and off, and with the fix. The finder's and the independent reproduction's JUnits also on branch_10x657014b3c579(its head before the branch was decommissioned on 2026-10-29), 10.5.2, 9.12.3 and (amain()variant) the 10.6.0 RC1 core jar, the commit released as 10.6.0; the one-test repro on the 10.6.0, 10.5.2 and 9.12.3 release jars, with-eaand-da(the fact check); a reflection-based history probe on all 104 releases from 5.2.1 to 10.5.2, with-eaand-da. OpenJDK 25.0.3, Linux aarch64.
- Finding record
a Lucene main checkout (645d4fe14a20): lucene/core/src/java/org/apache/lucene/geo/Rectangle.java:82-97 (constructor javadoc :82-84; GeoUtils.checkLatitude/checkLongitude :86-89; 'assert maxLat >= minLat' :94; dateline NOTE :96); lucene/core/src/java/org/apache/lucene/geo/XYRectangle.java:36-44 (IllegalArgumentException 'minY must be lower than maxY'); lucene/core/src/java/org/apache/lucene/document/LatLonShape.java:239-283 (newBoxQuery builds the Rectangle at :258, newSlowDocValuesBoxQuery at :283); lucene/core/src/java/org/apache/lucene/geo/SimpleWKTShapeParser.java:252-264 (parseBBox: ENVELOPE(minLon, maxLon, maxLat, minLat))GitHub (read-only, 2026-10-29): apache/lucene#10591 and apache/lucene-solr#2131 (LUCENE-9552); elastic/elasticsearch#32857 (AssertionError at org.apache.lucene.geo.Rectangle.<init> from that project's geohash bbox code, 'trying to construct a rectangle with a swapped high/low latitude') and elastic/elasticsearch#32891 (its fix in that project); apache/lucene PRs #16926-#16938 file lists; repos/apache/lucene/commits/main
- The finding in 30 seconds
lucene/core/src/java/org/apache/lucene/geo/Rectangle.java:38-39, (at 645d4fe14a20) 67-71, 82-97; geo/XYRectangle.java:36-44; geo/GeoUtils.java:85-89; geo/SimpleWKTShapeParser.java:35, 252-264lucene/core/src/java/org/apache/lucene/document/LatLonShape.java:238-283; (at 645d4fe14a20) document/LatLonShapeBoundingBoxQuery.java:240-246; document/SpatialQuery.java:824-827, 864-869; document/LatLonPoint.java:220-262
- Why it looks unintended
lucene/core/src/java/org/apache/lucene/geo/Rectangle.java:38-39, (at 645d4fe14a20) 67-71, 82-84; geo/GeoUtils.java:85-92; geo/Polygon.java:66-90; geo/Line.java:55-64; geo/GeoEncodingUtils.java:237-240; document/LatLonPoint.java:207-219; lucene/core/src/test/org/apache/lucene/geo/TestXYRectangle.java:36-37lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoRectangle.java:111-112; (at 645d4fe14a20) lucene/sandbox/src/java/org/apache/lucene/sandbox/document/LatLonBoundingBox.java:124-128; lucene/test-framework/src/java/org/apache/lucene/tests/geo/GeoTestUtil.java:495-499
- Suggested fix
lucene/core/src/java/org/apache/lucene/geo/GeoEncodingUtils.java:237-240; (at 645d4fe14a20) document/LatLonShapeDocValues.java:66-71
- TestRectangleInvertedLatitude.java (lucene/core, 1 test):
repro/LFV-1014/src/org/apache/lucene/geo/TestRectangleInvertedLatitude.java(27 lines)
It is made in parallel with this doc; every on-screen value comes from the code at 645d4fe14a20 or the diagram's executed probe.
assets/LFV-1014/bug.png(518 KB)
