All findings · Lucene finding · LFV-1134 · circle2d-contains-ignores-dateline-gap

LatLonShape WITHIN a circle that crosses the dateline matches a line that runs through longitude 0

A LatLonShape WITHIN query with a circle that crosses the dateline returns a line whose ends lie in the circle on both sides of the dateline, although Lucene draws the line straight through longitude 0. A 100 km circle around (0, 180) returns the line from lon -179.5 to 179.5: 1 hit instead of 0.

Summary

Priority
Low, rank 428WITHIN distance queries on LatLonShape with a circle that crosses the dateline match a line or polygon whose vertices straddle the dateline although its edges run the long way through longitude 0, in every release since 8.5.0. Such a shape spans almost the whole globe and is usually meant as the short path, for which the hit is the expected answer, so it is contrived and minor.
Impact
minor
Since
8.5.0 (as old as the API, LUCENE-8707)introduced 2020-02-19 · about 6 years
Affected
8.5.0 to 10.6.0 and main; not fixed upstream
Status
documented
Found by
non-formal: code reading of Circle2D, witnessed by a one-test public-API 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

A circle that crosses the dateline covers two strips of longitude, one at each edge of the lon/lat map. For WITHIN, Circle2D accepts a line or triangle when every vertex lies in either strip, so an edge from one strip to the other, which runs across the whole map, counts as inside.

Background: shapes, WITHIN and the dateline

  1. LatLonShape indexes a line or polygon as segments and triangles whose edges are straight lines in (lon, lat) degrees; longitude runs from -180 to 180 and does not wrap.
  2. A WITHIN query matches a document only if every indexed segment and triangle of its shape lies inside the query shape.
  3. A circle centred near the dateline is cut by it: in lon/lat it becomes two pieces, one just west of 180 and one just east of -180.
Diagram of the bug: a longitude-latitude map from -180 to 180; the 100 km query circle around latitude 0, longitude 180 is cut by the dateline into two pieces at the left and right edges of the map; the indexed line from longitude -179.5 to 179.5 has one end in each piece and runs straight across the map through longitude 0, far outside the circle, yet the WITHIN query returns it because only its end points are checked.
The lon/lat map from -180 to 180: the 100 km circle around (0, 180) shows as two pieces at the map's edges, and the indexed line joins its two ends, one in each piece, straight across the map through (0, 0); WITHIN still reports it inside. Select the still to open it at full size. Generated with AI, checked against the code.

The finding, step by step

  1. Index one LatLonShape line from (lat 0, lon -179.5) to (lat 0, lon 179.5). Lucene's edges are straight in lon/lat and do not wrap at ±180, so this edge runs the long way, through (0, 0): it matches INTERSECTS a 100 km circle around (0, 0).
  2. Run LatLonShape.newDistanceQuery with WITHIN and a 100 km circle around (0, 180). Both ends are 55.6 km from the centre, inside, but only 0.8 of the edge's 359 degrees of longitude lie inside it; (0, 0) is 20,015 km away. You expect 0 hits.
  3. You get 1 hit and no error, with assertions on and off. Lucene's own dateline-crossing box query just around the circle (lat ±0.9, lon 179.1 to -179.1) returns 0.
  4. Why: the circle's bounding box crosses the dateline, so Circle2D treats it as two longitude pieces, [179.1, 180] and [-180, -179.1]. WITHIN accepts a line or triangle once every vertex lies in either piece; nothing checks that the vertices are in the same piece, so an edge that leaves one piece and crosses the map to the other is accepted.

Who is affected

WITHIN distance queries on LatLonShape lines or polygons, with a circle whose box crosses the dateline (it reaches past ±180, not a pole), and a shape with vertices inside the circle on both sides. Default config; silent. Such a shape spans at least 360° minus the circle's longitude width (about 358° here), usually meant as the short path: contrived (Low).

Mechanism, terms and numbers729 words

LatLonShape.newDistanceQuery(field, WITHIN, circle) matches a document only if every indexed segment and triangle of its shape is inside the circle, and asks Circle2D.containsLine / containsTriangle for each. When the circle's bounding box crosses the dateline, Circle2D tests points against two longitude pieces, [minLon, 180] and [-180, maxLon], and these two methods return true once every vertex passes that test, without checking that the vertices lie in the same piece. Lucene's indexed edges are straight lines in lon/lat that do not wrap at ±180, so an edge with a vertex in each piece runs across the whole map, through longitude 0, far outside the circle, and the document is still returned as WITHIN. In the one-test repro a line from (0, -179.5) to (0, 179.5), which Lucene itself places through (0, 0), is WITHIN a 100 km circle around (0, 180): 1 hit instead of 0, silently, with assertions on and off. Lucene's own dateline-crossing box query just around the circle, and two polygons split at the 180th meridian over the same box, answer 0. The trigger is contrived (such a shape has an edge that spans at least 360 degrees minus the circle's longitude width, about 358 for the repro's circle, and whoever indexes it usually meant the short path), so the priority is Low. Present since the API was added (8.5.0); not fixed upstream. Requiring all vertices to be in the same piece fixes the repro (the unsplit shape now gives 0) and keeps the geo and shape suites green; the split form of the shape still matches.

The mechanism (main 5adff78bb168, lucene/core/src/java/org/apache/lucene/)

  1. WITHIN uses per-edge containment. SpatialQuery uses within() as the leaf predicate for WITHIN (document/SpatialQuery.java:123-135). For an indexed LINE it calls component2D.containsLine (document/LatLonShapeQuery.java:139-146, call at :145), for a TRIANGLE containsTriangle (:147-156, call at :155).
  2. Contract. Component2D: "return true if this component2D contains the provided line" and "... triangle" (geo/Component2D.java:73, :84).
  3. Two pieces. HaversinDistance sets crossesDateline = rectangle.minLon > rectangle.maxLon (geo/Circle2D.java:418); contains(x, y) then accepts a point only inside [minLon, 180] or [-180, maxLon] and within the haversine radius (:426-447, the pieces at :429-437). For Circle(0, 180, 100 km): [179.1007, 180] and [-180, -179.1007], latitudes ±0.8993.
  4. The defect (fix site). containsLine (geo/Circle2D.java:110-123) runs the bounding-box disjoint test (:119-121), then return contains(aX, aY) && contains(bX, bY); (:122). containsTriangle (:126-141) does the same for three vertices (:140). Nothing checks that the vertices are in the same piece.
  5. Of the edge tests, only intersectsLine has a dateline step. HaversinDistance branches on crossesDateline in its point and box tests (contains, disjoint, within, getMinX, getMaxX: geo/Circle2D.java:428, :463, :497, :531, :540), and intersectsLine retries with the centre shifted by 360 degrees for a crossing circle (:449-459, the retry at :454-457; LUCENE-9250, #10290). containsLine and containsTriangle got no such step.
  6. The box query does it right. Its containsLine / containsTriangle require all vertices on the same side when the box wraps (document/SpatialQuery.java:911-921, :923-933), and Rectangle2D.create builds a crossing box from two components (geo/Rectangle2D.java:299-316).
  7. Why the tests are silent. The random shape tests use the query component's own containsLine / containsTriangle as the WITHIN oracle (BaseSpatialTestCase.java:769, :781).

The numbers (the repro)

  • Line A = (0, -179.5), B = (0, 179.5): one segment covering 359 degrees of longitude. A and B are each 55.6 km from (0, 180); the short path between them is 111.2 km.
  • Lucene's edge passes (0, 0), 20,015 km from the centre. Only the 0.4 degrees next to the dateline at each end, 0.8 of 359 degrees (0.22% of the edge), are inside the circle.
  • Component level: containsLine(-179.5, 0, 179.5, 0) is true in both orders while contains(0, 0) is false; a triangle with vertices (-179.5, 0), (179.5, 0), (179.5, 0.1) gives containsTriangle true.
  • Query level: WITHIN Circle(0, 180, 100 km) 1; INTERSECTS Circle(0, 0, 100 km) 1; WITHIN the dateline-crossing box lat [-0.9, 0.9], lon [179.1, -179.1] (rounded out from the circle's box) 0; WITHIN the two polygons split at the 180th meridian 0.
  • The documented split form of the line (two parts meeting at the 180th meridian): WITHIN the circle 1, before and after the fix.
Exposure and likelihood258 words
  • Who is hit. Users of LatLonShape.newDistanceQuery (or newGeometryQuery with a Circle) with WITHIN on line or polygon fields, when the circle's bounding box crosses the dateline (the circle reaches past ±180 but not a pole, since a circle that reaches a pole gets the full longitude range and no crossing, geo/Rectangle.java:156-171; 100 km around (0, 180) in the repro) and the indexed shape has vertices inside the circle on both sides of the dateline.
  • Default, not opt-in. Default codec, IndexWriterConfig and searcher; one document; assertions on or off; no error and nothing wrong in the index.
  • Effect. A false WITHIN match: in the repro 99.8% of the edge's longitude span is outside the circle. The same document also INTERSECTS a 100 km circle at (0, 0), so Lucene's answers contradict each other.
  • How likely: contrived. Such a shape has an edge that spans at least 360 degrees minus the circle's longitude width (about 358 for the repro's 100 km circle), and every vertex lies inside the circle, so close to the dateline (within 0.9 degrees of it in the repro). Whoever indexes it by mistake usually meant the short path across the dateline, and for that shape the hit is the answer they want. No public report was found in the recorded searches (2026-11-02).
  • Not affected. Point documents, shapes whose vertices are all on one side, circles that do not cross the dateline, XYShape circles (Cartesian, no dateline), and the doc-values shape query, whose only public entry (LatLonShape.newSlowDocValuesBoxQuery) takes a box, not a circle.

Minimal repro

One JUnit test in lucene/core, public API only: index one two-point line, run one WITHIN distance query, expect 0 hits. Fixed geometry, one document, single thread; fails with assertions on and off.

TestLatLonShapeWithinDatelineCircle.java (lucene/core, public API)52 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.document;

import org.apache.lucene.document.ShapeField.QueryRelation;
import org.apache.lucene.geo.Circle;
import org.apache.lucene.geo.Line;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;
import org.apache.lucene.tests.util.LuceneTestCase;

public class TestLatLonShapeWithinDatelineCircle extends LuceneTestCase {

  public void testLineThroughLon0IsNotWithinDatelineCircle() throws Exception {
    try (Directory dir = newDirectory();
        IndexWriter w = new IndexWriter(dir, new IndexWriterConfig())) {
      Document doc = new Document();
      for (Field f :
          LatLonShape.createIndexableFields(
              "shape", new Line(new double[] {0, 0}, new double[] {-179.5, 179.5}))) {
        doc.add(f);
      }
      w.addDocument(doc);
      try (DirectoryReader reader = DirectoryReader.open(w)) {
        // expected 0: the indexed edge runs straight through lon 0, far outside the circle
        assertEquals(
            0,
            new IndexSearcher(reader)
                .count(
                    LatLonShape.newDistanceQuery(
                        "shape", QueryRelation.WITHIN, new Circle(0, 180, 100000))));
      }
    }
  }
}
Command
cp $REPRO/TestLatLonShapeWithinDatelineCircle.java $LUCENE/lucene/core/src/test/org/apache/lucene/document/
cd $LUCENE && ./gradlew :lucene:core:test --tests TestLatLonShapeWithinDatelineCircle
# fails the same way with -Ptests.asserts=false
Expected
0 hits: the indexed edge runs through lon 0, far outside the 100 km circle around (0, 180).
Observed
1 hit, no error: expected:<0> but was:<1>, with assertions on and off.

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

More files, environment, full output180 words

The hunt's minimal repro with one change: the comment's trailing "; actual 1" is dropped, because that line was 101 characters and failed google-java-format. With the change the test passes :lucene:core:check -x test. The test framework still randomizes codec, locale and time zone per seed; every recorded run, over several seeds, failed the same way.

Executed: gradle on main 5adff78bb168 with assertions on and off: fails at the WITHIN assertion (line 43); with the fix it passes. The hunt's standalone runs, a separate re-run and the audit's re-run of the earlier form (an INTERSECTS check first, then the same WITHIN assertion) fail the same way.

Use the same test as the regression test.

Environment

lucenemain 5adff78bb168 (12.0.0-SNAPSHOT)
jdkOpenJDK 25.0.3 (build 25.0.3+9-LTS)
osLinux, aarch64 (only this platform was run)
Command (environment)
cp $REPRO/TestLatLonShapeWithinDatelineCircle.java $LUCENE/lucene/core/src/test/org/apache/lucene/document/
cd $LUCENE && ./gradlew :lucene:core:test --tests TestLatLonShapeWithinDatelineCircle
# fails the same way with -Ptests.asserts=false

Observed output

TestLatLonShapeWithinDatelineCircle > testLineThroughLon0IsNotWithinDatelineCircle FAILED
    java.lang.AssertionError: expected:<0> but was:<1>
        at __randomizedtesting.SeedInfo.seed([5EED1134:11C70AACBE40AD10]:0)
        at org.apache.lucene.document.TestLatLonShapeWithinDatelineCircle.testLineThroughLon0IsNotWithinDatelineCircle(TestLatLonShapeWithinDatelineCircle.java:43)

# with the fix: BUILD SUCCESSFUL, the test passes with assertions on and off

Why it looks unintended

  • The same document is WITHIN a 100 km circle at (0, 180) and INTERSECTS one at (0, 0), 20,015 km away.
  • Component2D promises containment of the whole line or triangle; the Circle javadoc says "Dateline crossing is handled automatically".
  • The same commit wrote the box query's containment, which keeps a same-side dateline check; the box rejects this document.
The full argument258 words
  • Internal contradiction. One document is WITHIN Circle(0, 180, 100 km) and INTERSECTS Circle(0, 0, 100 km); their centres are 20,015 km apart and each radius is 100 km.
  • The edge model is Lucene's own. Indexed edges do not wrap at ±180: as Ignacio Vera wrote on #9767 (LUCENE-8721, 2019), "a line on the dateline will never intersects any other shape on the other side but I see that more like a feature". INTERSECTS follows that model; WITHIN does not.
  • Contracts. "return true if this component2D contains the provided line" (geo/Component2D.java:73, triangle :84). The Circle javadoc: "Dateline crossing is handled automatically" (geo/Circle.java:34, since 10.3.0, #14534); only "very large circles or circles near poles" are flagged (:33), and a 100 km circle on the equator is neither.
  • Same commit, two behaviours. LUCENE-9273 (f914e08b364) wrote today's vertex-only Circle2D containment and, in the same change, wrote the box query's containsLine / containsTriangle, which keep the box's existing rule that all vertices lie on the same side of a box that wraps the dateline (before the change the box query already tested a triangle against each side separately; now document/SpatialQuery.java:911-933).
  • The documented form is unaffected. The split form of the shape (two parts that meet at the 180th meridian) returns 1 for WITHIN the circle before and after the fix. The fix does change unsplit shapes like the repro's from 1 to 0: a user who stored one meaning the short path loses that hit. That is the fix's compatibility cost.
The devil's-advocate check191 words

Strongest case for "intended": whoever indexes (0, -179.5)-(0, 179.5) almost always means the 1-degree path across the dateline, which does lie in the circle; the Polygon javadoc says polygons "cannot cross the 180th meridian" (geo/Polygon.java:36); newLineQuery / newPolygonQuery say "does not support dateline crossing" (document/LatLonShape.java:286-301); Circle is @lucene.experimental (geo/Circle.java:47).

Why it fails: the indexed Line has no dateline precondition, and in Lucene's model it does not cross the dateline at all: Lucene places its edge through lon 0 and answers INTERSECTS accordingly. The dateline notes concern polygons and query-side geometries. @lucene.experimental allows API changes, not wrong answers. Lucene's own wrapping box rejects the same document. Every verifier, two of them from another model family (OpenAI GPT-5.6, which rated the argument weak), and the adjudication (at medium confidence) rejected it; a later fact check by the same model family rated it moderate as a "won't fix" argument (rare trigger, compatibility cost) and weak as a reason the current answer is right. Verdict: not intended, a defect. The split form of the shape is unaffected by the fix, which only turns the unsplit shape's 1 into 0.

Suggested fix

Recommended Require all vertices in the same piece (recommended)

In Circle2D.containsLine / containsTriangle, also require the vertices to lie in the same longitude piece when the circle crosses the dateline (a samePiece check on the distance calculator, always true otherwise). The repro then passes (the unsplit shape gives 0), 53 geo and shape suites stay green, and the split form of the shape still matches.

// containsLine (Circle2D.java:122)
return contains(aX, aY) && contains(bX, bY) && calculator.samePiece(aX, bX);

// HaversinDistance
@Override
public boolean samePiece(double aX, double bX) {
  return crossesDateline == false || (aX >= rectangle.minLon) == (bX >= rectangle.minLon);
}
All options and trade-offs3 options · 452 words

If two contained vertices lie in different pieces, the lon/lat-straight edge between them crosses the whole gap, which is outside the circle's bounding box, so by this argument rejecting it adds no false negative (an argument over Lucene's edge model, not an exhaustive test; the suites found none). One fix was built and tested in a scratch worktree of 5adff78bb168; the alternatives are listed with their trade-offs.

Require all vertices in the same piece (recommended) (validated by the checks)

Add a samePiece(aX, bX) default method to Circle2D.DistanceCalculator returning true (Cartesian calculator and non-crossing circles unchanged), override it in HaversinDistance, and require it in containsLine and, for a-b and b-c (which covers a-c), in containsTriangle. Full patch: +19/-2 in geo/Circle2D.java.

// containsLine (Circle2D.java:122)
return contains(aX, aY) && contains(bX, bY) && calculator.samePiece(aX, bX);

// HaversinDistance
@Override
public boolean samePiece(double aX, double bX) {
  return crossesDateline == false || (aX >= rectangle.minLon) == (bX >= rectangle.minLon);
}

Trade-offs

Exact for the case it changes; shapes with all vertices in one piece and the split form are answered as before. Behaviour change: unsplit shapes with contained vertices on both sides of the dateline go from WITHIN 1 to 0 (executed for the repro's line and a polygon with the same vertex longitudes), so a user who stored one meaning the short path loses that hit. Cost: one or two comparisons per contained segment or triangle. Not run: the full Lucene test suite. Variant (not run): reject when the segment's bounding box, which containsLine already receives, spans the gap (minX <= maxLon && maxX >= minLon); same result, still needs a calculator method.

What the checks validated

Scratch worktree of 5adff78bb168, gradle, OpenJDK 25.0.3: the repro fails stock and passes fixed, with assertions on and off; 53 geo and shape suites (org.apache.lucene.geo.*, document.TestLatLon*, TestXY*, TestShape*, search.TestLatLon*, TestXY*; 585 tests, 40 skipped, 0 failures, random root seed; not the whole Lucene suite) pass; :lucene:core:check -x test (google-java-format, ecj lint, forbidden APIs) passes. The hunt's 7-case component matrix, the intended-lens review's line and polygon tests and the devil's-advocate probe (unsplit line WITHIN 0, split form 1) also pass with it.

Build a crossing circle from two components

As Rectangle2D.create does for a crossing box, represent the circle as the circle clipped to each longitude piece and combine the two.

Trade-offs

Closer to how boxes work, but a larger change that touches every Circle2D relation. Not implemented or run.

Document it

State that indexed shapes queried WITHIN a dateline-crossing circle must be split at the dateline.

Trade-offs

Not recommended: leaves the contradiction with INTERSECTS and with the box query, and with "Dateline crossing is handled automatically".

How it was found

Code reading of Circle2D noticed that, of the edge tests, only intersectsLine has a step for a circle that crosses the dateline, while containment checks vertices only; a one-test public-API JUnit witnessed it. An independent reproducer, cross-model reviews (OpenAI GPT-5.6), a devil's-advocate verifier and an adjudication confirmed it with their own tests.

The method in full155 words

Method: non-formal (code reading of Circle2D, witnessed by a one-test public-API JUnit on the stock main jars).

  • The reading compared the dateline handling of the circle's methods: contains splits a crossing circle into two longitude pieces, intersectsLine retries with the centre shifted by 360 degrees, but containsLine / containsTriangle only test each vertex. A JUnit with a line across the gap witnessed the false WITHIN match on main, 10.6.0 and 9.12.3, and a same-piece fix sketch made it pass.
  • Then an independent reproducer wrote its own test with other numbers, cross-model reviews (OpenAI GPT-5.6, refute and intended lenses, executed) wrote and ran their own tests, a devil's-advocate verifier compared the unsplit and split shapes against the circle, the box and the split polygons, and an adjudication re-ran a test on clean main. An audit and a final repro check followed.
  • Earlier hunt coverage of the geo shapes had listed haversine circles as not checked.

Prior reports, history and checks

Prior reports

No prior report found in GitHub issues, PRs and commits, the JIRA archive, four Lucene mailing lists, downstream repositories or the web (searched 2026-11-02). Nearest: #10290 fixed the dateline for intersectsLine only; #9782 reports wrong WITHIN results at Polygon2D for shapes that share the query's boundary, a related symptom in the opposite direction, not this bug.

Searches3 searches

Related, not reports of this bug: #10290 (LUCENE-9250, dateline for Circle2D.intersectsLine, fixed in 8.5.0); #10313 (LUCENE-9273, wrote today's containment, no dateline discussion); #9753 (LUCENE-8707, the API); #9715 and #9767 (query polygons and lines at the dateline, not circles); #9601 (dateline boxes split in two); #9782 (LUCENE-8736, wrong WITHIN at Polygon2D for shared boundaries, false negatives, open); #10279 and #10909 (other Circle2D defects). LFV-1128 is a separate Circle2D bug (the intersectsLine closest point).

What was searched, when, and what turned up
WhereWhenHowFound
GitHub issues, PRs and commits (apache/lucene incl. the migrated JIRA archive, apache/lucene-solr, apache/solr, Elasticsearch, OpenSearch)2026-11-02three scripted prior-report searches (43, 51 and 37 queries: Circle2D, containsLine, dateline, antimeridian, WITHIN, newDistanceQuery, the javadoc phrase); GitHub searches; the review threads of lucene-solr #587, #1289 and #1341no report; nearest items are other bugs
Open PRs and upstream commits2026-11-02the file lists of all 408 open apache/lucene PRs; the newest commits on Circle2D.java; a re-check of issues and PRs updated since 2026-10-31no PR touches the files; newest commit f22f2b70a7d (#14898, a typo fix, July 2025)
Mailing lists (dev@, java-user@, issues@, commits@), the JIRA archive and the web2026-11-02list archive and JIRA queries, web queriesno user report found

History

As old as the API: LUCENE-8707 (2020-02-19) added Circle2D with vertex-only containment, first released in 8.5.0; today's code is from LUCENE-9273 (8.6.0). Fails on every executed release from 8.5.0 to 10.6.0 and on main; not new in 10.6; not fixed upstream.

Versions tested6 rows

Origin: d48bafb2999 on master and its branch_8x cherry-pick 50168ab5bc8 (LUCENE-8707 "Add LatLonShape and XYShape distance query", lucene-solr#587 by Ignacio Vera, author date 2020-02-19). Current form: f914e08b364 / a001f135f22 (LUCENE-9273, lucene-solr#1341, 2020-04-20, in 8.6.0) kept the vertex-only rule; later only reformatting (LUCENE-9570, 2d6ad2fee6d). LUCENE-9250 (988ce9bff7e / 16663db0996, released in 8.5.0 by the branch_8x commit 16663db0996) added dateline handling to intersectsLine only. geo/Circle2D.java is the same blob (8fd98adbc55) at 10.6.0 and 5adff78bb168. The release runs used the earlier form of the test (an INTERSECTS check, then the same WITHIN assertion; for 8.x and 9.0.0 with the pre-9.1 LuceneTestCase import). Releases between the executed ones were not run; they are assessed from code history (tag ancestry and reading).

Versions and branches executed, with results
Version or commitVerdictResult
main 5adff78bb168 affectedthis doc's test fails through gradle with assertions on and off; passes with the fix
10.0.0, 10.6.0 affectedexpected:<0> but was:<1> at the WITHIN assertion (release jars)
9.0.0, 9.12.3 affectedthe same failure
8.6.0, 8.11.4 affectedthe same failure (8.6.0 is the first release with today's containsLine / containsTriangle)
8.5.0, 8.5.2 affectedthe same failure (first releases with the API); an 8.5.0 counterfactual that rejects end points in different pieces passes
8.4.1 API absentcompile error: no Circle, no LatLonShape.newDistanceQuery

Independent checks

An independent reproducer, two executed cross-model reviews (OpenAI GPT-5.6), a devil's-advocate verifier and an adjudication reproduced it with their own tests and numbers and rejected the intended-behaviour case; an audit and a final repro check confirmed it. This doc's gradle runs reproduced it on main and tested the fix.

Checks and verdicts414 words

Every executed check saw the false match come from vertex-only containment of a crossing circle, and every one that ran the same-piece fix saw its test pass. The cross-model reviews ran on main ea57360912ed, whose Circle2D.java is the same blob as at 5adff78bb168; the intended-lens review's own test was re-run on 5adff78bb168 after the review, and this doc's fact check re-ran the refute-lens review's test there (3 tests run, 1 failure, expected:<0> but was:<1>, the same result as before).

Each independent check and its verdict
CheckVerdictWhat it found
finder reproducedThe one-test JUnit fails on main (on, off), 10.6.0 and 9.12.3; passes with the same-piece fix; a 7-case component matrix shows containsLine true while contains(0, 0) is false, and the dateline Rectangle answers false.
cross-model review, refute lens real (high)OpenAI GPT-5.6, executed: its own test (lon ±179.2, a 150 km circle on the dateline, a witness that the edge crosses lon 0, same-piece and outside-end controls) returns WITHIN 1 instead of 0 on main ea57360912ed, 10.6.0 and 9.12.3, and again on 5adff78bb168 in this doc's fact check; passes with the fix.
independent reproducer realOwn test (lat 10, lon -179 to 179, 150 km circle at (10, 179.9)) fails on main (on, off), 10.6.0, 10.5.2 and 9.12.3 and passes with the fix; containsLine and containsTriangle true while contains(0, 10) is false.
cross-model review, intended lens real (high)OpenAI GPT-5.6, executed: its own test (lon ±179.4, 120 km circle at (0, 180)) fails for a line and a polygon, 3 controls pass; passes with the fix while TestCircle2D passes 7 of 7; intended-behaviour argument rated weak.
devil's-advocate verifier realUnsplit line: WITHIN the circle 1, WITHIN the dateline box 0, WITHIN the split polygons 0; the split line gives 1 everywhere, before and after the fix. Rejected the short-path reading, the dateline notes, @lucene.experimental and the spherical-model note.
adjudication confirmed (medium), LowRe-ran the reproducer's test on clean main with assertions off: fail.
audit and final repro check confirmedThe audit re-ran the test on main with assertions off (fail); the final check reduced the repro to this one test, which fails on main and passes with the fix, and set the impact class to minor.
this doc reproduced; fix validatedGradle on main 5adff78bb168: the test fails with assertions on and off; with the fix it passes, 53 geo and shape suites pass and :lucene:core:check -x test passes.

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,398 words

Finding record

LFV-1134, snapshot of 2026-11-06

Registry title
LatLonShape WITHIN a circle that crosses the dateline matches a line or polygon whose vertices lie inside the circle on both sides of the dateline, although Lucene runs its edges through longitude 0, far outside the circle: Circle2D.containsLine/containsTriangle test only the vertices
Area
core / geo (Circle2D.containsLine/containsTriangle for haversine circles whose bounding box crosses the dateline, behind LatLonShape.newDistanceQuery WITHIN)
Priority
Low, rank 428: WITHIN distance queries on LatLonShape with a circle that crosses the dateline match a line or polygon whose vertices straddle the dateline although its edges run the long way through longitude 0, in every release since 8.5.0. Such a shape spans almost the whole globe and is usually meant as the short path, for which the hit is the expected answer, so it is contrived and minor.
Impact class
minor
Impact note
Minor: only WITHIN distance queries on LatLonShape fields (LatLonShape.newDistanceQuery; the doc-values shape query takes no circle) whose circle's bounding box crosses the dateline are hit, with the default codec, IndexWriterConfig and searcher, -ea and -da alike: a line or polygon document whose vertices all lie in the circle, some on each side of the dateline, is silently returned as WITHIN although Lucene's straight lon/lat edges carry it at least 360 degrees minus the circle's longitude width (about 358 for the repro's circle) through lon 0; any circle whose bounding box crosses the dateline (it reaches past ±180 but not a pole) triggers it (100 km on the equator in the repro), but such a shape is unusual, and a user who indexes one by mistake usually meant the short path across the dateline, for which the hit is the answer they want.
Summary
You index a two-point LatLonShape line from (lat 0, lon -179.5) to (lat 0, lon 179.5); Lucene runs its edge straight through (0, 0), so the document matches INTERSECTS a 100 km circle around (0, 0). A WITHIN query with a 100 km circle around (0, 180), 20,000 km from that point, should then return 0 hits, but it returns 1 with no error. Circle2D treats a circle that crosses the dateline as two longitude pieces, and containsLine/containsTriangle accept a shape once every vertex lies in either piece, never checking that the edges between them stay inside.
Introduced
d48bafb2999542224c86f278d1fd0dcb949388cc on master and its branch_8x cherry-pick 50168ab5bc85c382a6d558553caa1f752bf5afc1 (LUCENE-8707 'Add LatLonShape and XYShape distance query', apache/lucene-solr#587 by iverase, opened 2019-02-25, merged 2020-02-19; JIRA LUCENE-8707 = apache/lucene#9753): the first Circle2D already reported a segment or triangle as inside when every vertex passed contains(), with no dateline check. Today's containsLine/containsTriangle bodies come from f914e08b3647ad91eb97ada6ebefe1e96ebd5a89 / a001f135f229a4c08afe4b6eb3dc11da5db91605 (LUCENE-9273, apache/lucene-solr#1341 = apache/lucene#10313, 2020-04-20, first in 8.6.0), which kept the vertex-only rule while giving the box query's containsLine/containsTriangle an explicit same-side check for boxes that cross the dateline; the only later change is reformatting (LUCENE-9570, 2d6ad2fee6d) [read: git show and git blame in the history clone; exec: the one test fails on 8.5.0, and an 8.5.0 counterfactual whose relateIndexedLineSegment rejects endpoints in different pieces passes] (2020-02-19 (author date 2020-02-19T16:03:30+01:00 of d48bafb2999 and 50168ab5bc8); current form f914e08b364, author date 2020-04-20)
First affected version
8.5.0 [exec: the one test fails on 8.5.0; 8.4.1 has no org.apache.lucene.geo.Circle and no LatLonShape.newDistanceQuery (compile error), so the defect is as old as the API and there is no earlier correct version]
Affected versions
every release since the API exists: 8.5.0-8.11.4, 9.0.0-9.12.3, 10.0.0-10.6.0 (released 2026-10-29, 4ae946e5a9b) and main 5adff78bb168 (12.0.0-SNAPSHOT). Executed with the one test: 8.5.0, 8.5.2, 8.6.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.6.0 and main (-ea and -da) all fail at the WITHIN assertion while the INTERSECTS anchor passes; the independent reproduction's own test also fails on 10.5.2, 10.6.0 and 9.12.3 [exec]; the other releases by tag ancestry and reading (no commit after f914e08b364 changed containsLine/containsTriangle except reformatting) [read]. Not new in 10.6 (8.5.0 and 10.5.2 fail). Not fixed upstream: Circle2D.java is the same blob (8fd98adbc55) at ea57360912ed, 5adff78bb168 and releases/lucene/10.6.0, its newest upstream commit is f22f2b70a7d (author date 2025-07-07, a typo fix, #14898), and upstream main 1c2072f0284 (author date 2026-11-02) has no later change to it [read through the GitHub API, 2026-11-02]. Runs used 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
non-formal: code reading
Upstream and prior reports
none found (2026-11-02, on top of the finder's, the independent reproduction's, the intended-behaviour check's and the adjudication's GitHub searches; rechecked the same day): scripted prior-report searches of 43 queries (the finder's) and 51 plus 37 queries (a follow-up search) over apache/lucene issues, PRs and commits, Elasticsearch, OpenSearch and Solr, the dev@, java-user@, issues@ and commits@ lists, the JIRA archive and the web (44, 330 and 93 distinct hits, none with high overlap), direct lists.apache.org and JIRA queries, the review threads of lucene-solr PRs #587, #1289 and #1341, and the file lists of all 408 open apache/lucene PRs (none touches Circle2D.java, Circle.java, Component2D.java, LatLonShapeQuery.java or LatLonShapeDocValuesQuery.java; nothing created or updated since 2026-10-31 is geo). Same-day recheck: GitHub search on apache/lucene for 'Circle2D' (only #14898, #10290, #41, #10909, #10279), 'containsLine' (#15356, #10313), 'WITHIN dateline' (15 hits; nearest #9715, #9601, #9782, none about circles), 'circle within dateline' (7, all old GeoPoint and LatLonPoint items), 'antimeridian' (#7624, #2855) and 'newDistanceQuery' (18, none about this), and the 38 issues and PRs updated since 2026-10-31 (none mentions geo, circle, shape, latlon, dateline, distance or within); the newest commit on Circle2D.java is still f22f2b70a7d (2025-07-07). Related, not reports: #10290 / lucene-solr#1289 (LUCENE-9250, dateline handling for Circle2D.intersectsLine only, fixed in 8.5.0), #10313 / lucene-solr#1341 (LUCENE-9273, wrote today's containsLine/containsTriangle, no dateline discussion), #9753 / lucene-solr#587 (LUCENE-8707, the API), #9715 (LUCENE-8669, WITHIN with split query polygons at the dateline, fixed), #9767 (LUCENE-8721, query polygons and lines on the shared dateline, open), #9601 (LUCENE-8555, dateline boxes split in two), #9782 (LUCENE-8736, WITHIN with shared boundaries at Polygon2D, LFV-206), #10279 (LUCENE-9239) and #10909 (LUCENE-9870), other Circle2D defects.
Lucene commit tested
main 5adff78bb168 (12.0.0-SNAPSHOT, 2026-11-02) through gradle in a scratch worktree, with assertions on and off, without and with the fix (the repro, 53 geo and shape test suites with a random root seed, and :lucene:core:check -x test); the hunt ran the earlier form of the test on the 8.4.1 (API absent), 8.5.0, 8.5.2, 8.6.0, 8.11.4, 9.0.0, 9.12.3, 10.0.0 and 10.6.0 release jars, plus an 8.5.0 counterfactual. Cross-model reviews ran on main ea57360912ed, whose Circle2D.java is the same blob; both reviews' tests were later re-run on 5adff78bb168 with the same result. Only Linux aarch64 with OpenJDK 25.0.3 was run.

Lucene and upstream sources

Finding record
  • lucene/core/src/java/org/apache/lucene/geo/Circle2D.java:110-123 (at 5adff78bb168) (containsLine: disjoint test, then contains(a) && contains(b), :122; fix site), :126-141 (containsTriangle, :140; fix site), :403-460 (HaversinDistance: crossesDateline = rectangle.minLon > rectangle.maxLon; contains :426-447 accepts only the pieces [minLon, 180] and [-180, maxLon]; intersectsLine :450-459 shifts the centre by 360 degrees for a crossing circle)
  • lucene/core/src/java/org/apache/lucene/document/LatLonShapeQuery.java:145, (at 5adff78bb168) :155 (WITHIN leaf predicate: component2D.containsLine / containsTriangle per indexed segment or triangle); lucene/core/src/java/org/apache/lucene/geo/Component2D.java:73, :84 ('return true if this component2D contains the provided line / triangle'); lucene/core/src/java/org/apache/lucene/geo/Circle.java:29-47 (spherical model, 'For very large circles or circles near poles, consider using polygons instead', 'Dateline crossing is handled automatically' :34, @lucene.experimental :47)
  • lucene/core/src/java/org/apache/lucene/document/SpatialQuery.java:911-933 (at 5adff78bb168) (box containsLine/containsTriangle: same-side check when the box wraps the dateline); lucene/core/src/java/org/apache/lucene/geo/Rectangle2D.java:302-313 (a crossing box becomes two components); lucene/core/src/java/org/apache/lucene/geo/Polygon.java:36 and lucene/core/src/java/org/apache/lucene/document/LatLonShape.java:288, :296, :315-319 (dateline notes on polygons and query lines; newDistanceQuery); lucene/core/src/test/org/apache/lucene/document/BaseSpatialTestCase.java:769, :781 (the random tests' WITHIN oracle calls the same containsLine/containsTriangle)
The finding in 30 seconds
  • lucene/core/src/java/org/apache/lucene/document/SpatialQuery.java:123-135, (at 5adff78bb168) :911-933; document/LatLonShapeQuery.java:126-161
  • lucene/core/src/java/org/apache/lucene/geo/Circle2D.java:110-123, (at 5adff78bb168) :126-141 (fix site), :403-459, :463, :497, :531, :540; geo/Component2D.java:73, :84; geo/Rectangle2D.java:299-316
  • lucene/core/src/test/org/apache/lucene/document/BaseSpatialTestCase.java:769, (at 5adff78bb168) :781
Who is affected
  • lucene/core/src/java/org/apache/lucene/geo/Rectangle.java:156-171; (at 5adff78bb168) document/LatLonShape.java:263-284; document/LatLonShapeDocValuesQuery.java:31
Why it looks unintended
  • lucene/core/src/java/org/apache/lucene/geo/Component2D.java:73, (at 5adff78bb168) :84; geo/Circle.java:29-47; geo/Polygon.java:36; document/LatLonShape.java:286-301, :315-319; document/SpatialQuery.java:911-933
  • https://github.com/apache/lucene/issues/9767 (LUCENE-8721, Ignacio Vera's comment of 2019-03-13)
Suggested fix
  • lucene/core/src/java/org/apache/lucene/geo/Rectangle2D.java:299-316 (at 5adff78bb168)
History
  • a Lucene clone releases/lucene/10.6.0:lucene/core/src/java/org/apache/lucene/geo/Circle2D.java (blob 8fd98adbc55, the same as at 5adff78bb168)

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

Repro files

About the diagrams

A summary still (PNG).

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.