All findings · Lucene finding · LFV-1014 · geo-rectangle-inverted-latitude-assert-only

geo Rectangle checks minLat <= maxLat only with an assert, so a lat/lon box with swapped latitudes throws a bare AssertionError with -ea and is silently accepted with -da, while XYRectangle throws IllegalArgumentException

Swap the latitudes of a lat/lon box, as in LatLonShape.newBoxQuery(field, INTERSECTS, 10, 5, 0, 1), and Lucene does not reject it: the order is checked only by an assert. With -ea you get a bare AssertionError; with -da a query whose hits match no region, where XYRectangle throws IllegalArgumentException.

Summary

Priority
Low, rank 416With 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
wrong error
Since
5.4.0 (LUCENE-6780, 2015)introduced 2015-09-18 · over 11 years
Affected
5.4.0 to 10.6.0 and main
Status
documented
Found by
a scan for assert-only argument checks, then a JUnit

AI-assisted page: this finding was first signalled by code reading or testing and then checked with AI tools; the checks it went through, and what each found, are under Prior reports, history and checks. Not yet triaged by a human. Snapshot of 2026-11-06.

The finding in 30 seconds

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.

Background: lat/lon boxes and asserts

  1. new Rectangle(minLat, maxLat, minLon, maxLon) is a lat/lon box. minLon > maxLon is allowed and means the box crosses the dateline; no inversion of latitude is documented.
  2. LatLonShape.newBoxQuery and newSlowDocValuesBoxQuery build a Rectangle from their four arguments, and so does SimpleWKTShapeParser for the text ENVELOPE(minLon, maxLon, maxLat, minLat). LatLonPoint.newBoxQuery builds none.
  3. A Java assert runs 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).
Summary card: a lat/lon box between latitudes 5 and 10 passed with the latitudes swapped (minLat 10, maxLat 5) to LatLonShape.newBoxQuery on Lucene main. Expected: IllegalArgumentException, as XYRectangle gives. Actual: a bare AssertionError at Rectangle.java:94 with assertions on; with assertions off the box is accepted and INTERSECTS returns only the lat 0..20 polygon instead of the two polygons of the ordered box, while LatLonPoint.newBoxQuery returns nothing. The fix throws IllegalArgumentException when minLat > maxLat.
The diagram follows the four-polygon example on main 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.

The finding, step by step

  1. 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.
  2. You expect IllegalArgumentException: what new XYRectangle(0, 1, 10, 5) gives ("minY must be lower than maxY, got 10.0 > 5.0"), and what Rectangle itself gives for latitude 95.
  3. With assertions on (-ea; Lucene's gradle tests enable them for about 75% of seeds) you get a bare AssertionError, no message, from Rectangle.<init>. The same for new Rectangle(10, 5, 0, 1) and for the WKT text ENVELOPE(0, 1, 5, 10).
  4. 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.newBoxQuery with the same swapped box returns 0 hits.
  5. Why: the Rectangle constructor rejects an out-of-range coordinate with IllegalArgumentException, but checks the latitude order only with assert maxLat >= minLat. It has done so since the class first shipped, in 5.4.0 (2015).

Who is affected

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.

Terms

  • 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 by XYShape.
  • -ea / -da: JVM flags that turn Java assert statements on or off; off is the default.

The bug

  1. The constructor calls GeoUtils.checkLatitude(minLat), checkLatitude(maxLat), checkLongitude(minLon) and checkLongitude(maxLon) (Rectangle.java:86-89); each throws IllegalArgumentException for an out-of-range value. The only check of the order is assert maxLat >= minLat; (:94).
  2. LatLonShape.newBoxQuery (LatLonShape.java:258) and newSlowDocValuesBoxQuery (:283) pass the caller's four values straight into the constructor; SimpleWKTShapeParser.parseBBox reads ENVELOPE(minLon, maxLon, maxLat, minLat) and does the same (SimpleWKTShapeParser.java:252-264).
  3. With -ea that assert throws a bare java.lang.AssertionError with no message. With -da the box Rectangle(lat=10.0 TO 5.0 lon=0.0 TO 1.0) is kept as is.
  4. The shape query encodes it unchanged (LatLonShapeBoundingBoxQuery.java:240-246: min latitude 10, max latitude 5). Its point test y < 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.
  5. LatLonPoint.newBoxQuery builds no Rectangle: it encodes the range from latitude 10 up to 5, which is empty, so it returns 0 hits with -ea and -da (LatLonPoint.java:243-244).
  6. XYRectangle checks the same order with if (minY > maxY) throw new IllegalArgumentException("minY must be lower than maxY, got ...") (XYRectangle.java:41-44), and the same for X.

The numbers

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:

querybox 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, LatLonShape or LatLonPoint.newGeometryQuery with a Rectangle, or SimpleWKTShapeParser.parse of an ENVELOPE, that passes minLat > maxLat. Possible sources: box corners from a user or a map that the application forwards without ordering them, or WKT text, whose ENVELOPE(minLon, maxLon, maxLat, minLat) puts the two latitudes the other way round from the constructor's minLat, maxLat. How often this happens was not measured.
  • Default or opt-in. Default: no setting, any codec, any index. -da is 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 bare AssertionError with no message. It is an Error, so catch (IllegalArgumentException e) and catch (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.

Minimal repro

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.

TestRectangleInvertedLatitude.java (lucene/core, 1 test)27 lines
/*
 * 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));
  }
}
Command
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, as XYRectangle gives for minY > maxY and Rectangle gives for latitude 95.
Observed
With -ea a bare AssertionError at Rectangle.<init>(Rectangle.java:94); with -da no exception, the box is accepted.

All files of this repro: repro/LFV-1014/

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.

Environment

lucenemain 645d4fe14a20 (11.0.0-SNAPSHOT); the same outcome on the 10.6.0, 10.5.2 and 9.12.3 release jars
jdkOpenJDK 25.0.3 (build 25.0.3+9-LTS)
osLinux, aarch64 (the only platform run; the code path is plain Java argument checking)
Command (environment)
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

Observed output

# 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 failed

Why it looks unintended

  • XYRectangle had the same assert until LUCENE-9218 (8.5.0) replaced it with IllegalArgumentException("minY must be lower than maxY ..."); Rectangle was not changed.
  • The constructor already throws IllegalArgumentException for every out-of-range coordinate, and spatial3d GeoRectangle and sandbox LatLonBoundingBox reject minLat > maxLat.
  • No test or Lucene caller uses an inverted latitude, and the -da hits match no reading of the box.
The full argument313 words
  • The twin was changed on purpose. XYRectangle shipped in 8.2.0 with assert minX <= maxX; assert minY <= maxY; (sandbox, through 8.4.1). LUCENE-9218 (#10258, apache/lucene-solr#1252, Ignacio Vera, 4a54ffb553a on main and ca3319cdbce on branch_8x, in 8.5.0) replaced both with IllegalArgumentException. Neither the issue nor the PR mentions Rectangle. TestXYRectangle.java:36 documents the rule as "maxY must be gte minY" (in a method whose name does not start with test, so it never runs: LFV-1013).
  • Same class, other checks. Rectangle throws IllegalArgumentException for latitude 95 ("invalid latitude 95.0; must be between -90.0 and 90.0", GeoUtils.java:87).
  • Siblings. spatial3d GeoRectangle throws "Top latitude less than bottom latitude" (GeoRectangle.java:111-112); sandbox LatLonBoundingBox throws "cannot have minLat [..] exceed maxLat [..]" (LatLonBoundingBox.java:124-128); Polygon and Line validate their arguments with IllegalArgumentException (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, and GeoEncodingUtils.createSubBoxes returns an empty grid for a box that quantization inverts before it builds any Rectangle (GeoEncodingUtils.java:237-240; the guard dates from LUCENE-7661, 2017, and has run on the four doubles, before any Rectangle, since LUCENE-9641, 2021).
  • No meaning. With -da the 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 the assert for 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.

Suggested fix

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.

Throw IllegalArgumentException in the Rectangle constructor (validated by the checks) (recommended)

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 LatLonShape box factories, newGeometryQuery with a Rectangle and the WKT ENVELOPE. Matches XYRectangle, the constructor's own range checks and the sibling boxes.
  • Behaviour change: LatLonPoint.newGeometryQuery(field, INTERSECTS, new Rectangle(10, 5, ...)) now throws where -da gives 0 hits today (the intended-behaviour check's probe; other relations not run); with -ea it already threw AssertionError.
  • LatLonPoint.newBoxQuery and LatLonDocValuesField.newSlowBoxQuery keep returning 0 hits for the same box, because they never build a Rectangle; 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 Rectangle is 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 (AssertionError at Rectangle.java:94) and false (no exception).
  • Fix + the repro: the repro, the 17 upstream classes of the org.apache.lucene.geo test package and the 19 classes org.apache.lucene.document.TestLatLon* plus TestShapeDocValues: 38 suites, 351 tests, 0 failed, 22 skipped (20 @Nightly, 1 @Ignore, 1 @AwaitsFix), with -Ptests.asserts=true (seed DEADBEEF) and false (seed 5EED1014).
  • :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).

Treat the inverted box as empty in the LatLonShape box factories

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.

Check the order only in the callers

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.

Document the precondition only

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.

Downstream note

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.

How it was found

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, Rectangle against XYRectangle / XYShape.newBoxQuery and against LatLonPoint.newBoxQuery: an IllegalArgumentException on the Cartesian side, a bare AssertionError (-ea) or silent acceptance (-da) on the lat/lon side, 0 hits on the point side.
  • Callers. Grepping the constructor's callers added newSlowDocValuesBoxQuery and the WKT ENVELOPE path.
  • Reduction. The finder's 3-test JUnit was reduced to the one-line constructor test above.

Prior reports, history and checks

Prior reports

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.

What was searched, when, and what turned up
WhereWhenHowFound
GitHub issues and PRs (incl. the migrated JIRA archive), commits, downstream trackers2026-10-29two 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 PRs2026-10-29the 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, web2026-10-2940 queries over dev@, java-user@, issues@ and commits@; 8 JIRA and 6 web queriesno report

History

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.

Versions and branches executed, with results
Version or commitVerdictResult
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) affectedsame (on 9.12.3 the assert is at Rectangle.java:61); XYRectangle throws IllegalArgumentException
7.7.0 to 8.4.1 affectedRectangle, shape box and WKT: AssertionError (-ea), accepted (-da); XYRectangle (8.2.0+) assert-only too
5.4.0 to 7.6.0 affectedGeoRect / Rectangle: AssertionError (-ea), accepted (-da); 7.5.0-7.6.0 shape box accepted even with -ea
5.3.2 and older API absentno public 2D lat/lon rectangle class (spatial3d's separate GeoRectangle aside)
main + fix not affectedIllegalArgumentException with -ea and -da; geo and lat/lon suites pass

Independent checks

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.

Each independent check and its verdict
CheckVerdictWhat it found
finder candidateJUnit (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, mediumOpenAI 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 realOwn 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, mediumOpenAI 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, mediumRe-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 keepRe-ran the one-test repro (seed A0D17004) and the intended-behaviour check's probe on main.
this doc confirmed on 645d4fe14a20The one-test repro fails through gradle with assertions on and off; the fix validated with gradle.
fact check real; five slips, correctedRe-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, correctedOpenAI 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, correctedChecked 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.

Diagram file

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.

Sources and provenance

Finding record, Lucene and upstream sources, repro files and diagram notes1,182 words

Finding record

LFV-1014, snapshot of 2026-11-06

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_10x 657014b3c579 (its head before the branch was decommissioned on 2026-10-29), 10.5.2, 9.12.3 and (a main() 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 -ea and -da (the fact check); a reflection-based history probe on all 104 releases from 5.2.1 to 10.5.2, with -ea and -da. OpenJDK 25.0.3, Linux aarch64.

Lucene and upstream sources

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-264
  • lucene/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-37
  • lucene/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

Other statements trace to the hunt's working files (models, logs, reviews and briefs), which are not published.

Repro files

About the diagrams

It is made in parallel with this doc; every on-screen value comes from the code at 645d4fe14a20 or the diagram's executed probe.

Built 2026-11-06 from the hunt's record of this finding and its doc file. Lucene main under test in this hunt: ea57360912ed. Every listed finding was re-run on Lucene main e1faeba08bde on 2026-11-02.