One missing read of ): the parser handles a parenthesized point in the middle of a list, but not the first one (unless it is alone) or the last one.
- WKT (well-known text) writes a geometry as text:
POINT (1 2)is one point,LINESTRING (1 2, 3 4)a line through two points. - A multipoint is a set of points. The Simple Features grammar writes each member as its own point text in parentheses,
MULTIPOINT ((1 2), (3 4)); many tools also accept the older bare formMULTIPOINT (1 2, 3 4). - Lucene's
SimpleWKTShapeParser.parseturns WKT into Lucene shapes; a multipoint becomes adouble[][]with one{x, y}row per point.
MULTIPOINT ((1 2), (3 4)) as the parser reads them: the first point's ) is taken as the end of the list, so the next , arrives where a ) is expected. Select the still to open it at full size. Generated with AI, checked against the code.- Parse a two-point multipoint written the standard WKT way, each point in its own parentheses:
SimpleWKTShapeParser.parse("MULTIPOINT ((1 2), (3 4))"). - You expect
double[][] {{1, 2}, {3, 4}}. The same parser returns exactly that for the bare formMULTIPOINT (1 2, 3 4), and{{1, 2}}forMULTIPOINT ((1 2)). - Instead it throws
java.text.ParseException: expected ) but found: ','. It reads the first point's(,1and2, but not its). It takes that)as the end of the whole list, then finds,where it still wants the point's). - Position decides:
MULTIPOINT (1 2, (3 4), 5 6)parses, butMULTIPOINT (1 2, (3 4))fails withexpected word but found: END-OF-STREAM. 3D points and aGEOMETRYCOLLECTIONthat holds such a multipoint fail too. No wrong shape is ever returned.
Applications calling the public parse or parseExpectedType on multipoints from writers that use the standard form, such as PostGIS's ISO WKT writer since 2022. The geometry, or the collection holding it, is refused loudly: no wrong shape, no hang, no setting involved. In Lucene only tests call the parser; the bare form is a workaround.
Mechanism, terms and numbers706 words
SimpleWKTShapeParser, the public WKT reader in core org.apache.lucene.geo, says it parses WKT and complies with ISO/IEC 13249-3:2016. In the Simple Features WKT grammar each member of a multipoint is a point text in its own parentheses, MULTIPOINT ((10 10), (20 20)); the bare MULTIPOINT (10 10, 20 20) is the older, widely accepted variant. The parser accepts the bare form and a single parenthesized point, but MULTIPOINT ((1 2), (3 4)) throws java.text.ParseException: expected ) but found: ','. In parseCoordinates the code before the loop reads a parenthesized first point's ( and numbers but not its ); the loop condition then reads that ) as the end of the whole list, and the check after the loop finds the ,. Points read inside the loop get their ) checked at once, so acceptance depends on position: MULTIPOINT (1 2, (3 4), 5 6) parses, MULTIPOINT (1 2, (3 4)) fails with expected word but found: END-OF-STREAM. The failure is loud and never yields a wrong shape. It dates from the class's first commit (LUCENE-8538, 2018) and is in every release from 7.6.0 to 10.6.0 and on main. Reading each multipoint member as bare or ( point ) fixes it.
SimpleWKTShapeParser: a public class in coreorg.apache.lucene.geo(exported, no internal or experimental tag) with two entry points,parse(String)andparseExpectedType(String, ShapeType), both declaring a checkedjava.text.ParseException(SimpleWKTShapeParser.java:50-55at645d4fe14a20).parseCoordinates(:122-145): the shared helper that reads a comma-separated list of coordinates up to its closing). Multipoints, lines, polygon rings and holes all use it.nextCloserOrComma(:374-383): reads one token and returns it if it is,or). The loop over the list continues on,and stops on).
For MULTIPOINT ((1 2), (3 4)), after parseMultiPoint has read the list's ( (:161) and called parseCoordinates (:167):
:126: the next token is(, not a number, sonextWordreads it and setsisOpenParen = true;parseCoordinatereads1and2(:127). The point's)is not read.:130:nextCloserOrCommareads that), the first point's closer, so the loop ends at once: the parser now believes the whole list is closed.:141:isOpenParenis still true, sonextCloserreads the next token,,, and throwsParseException("expected ) but found: ','")(:351-352).
MULTIPOINT ((1 2)) passes by coincidence: the point's ) ends the loop and the list's ) satisfies the check. A parenthesized point read inside the loop has its ) checked at once (:135-138), and isOpenParen is reset each iteration (:131), so a mid-list one works. A parenthesized last point leaves isOpenParen set after the list's ) was read, so :141 asks for one more ) and hits the end of input: expected word but found: END-OF-STREAM (:294).
Polygon holes rely on the first-position branch: parsePolygonHole (:203-210) calls parseCoordinates with the hole's own ( unread, :126 swallows it, and :131 clears the flag at the hole's second coordinate. With the check after the loop, holes still parse; a fix that checks the first point's ) early must therefore also make parsePolygonHole read its own (.
| input | result |
|---|---|
MULTIPOINT (1 2, 3 4) | {{1, 2}, {3, 4}} |
MULTIPOINT ((1 2)) | {{1, 2}} |
MULTIPOINT ((1 2), (3 4)), MULTIPOINT((1 2),(3 4)) | ParseException: expected ) but found: ',' |
MULTIPOINT ((1 2 9), (3 4 9)) (3D) | same |
MULTIPOINT ((1 2), 3 4) | same |
MULTIPOINT (1 2, (3 4)) | ParseException: expected word but found: END-OF-STREAM |
MULTIPOINT (1 2, (3 4), 5 6) | parses |
GEOMETRYCOLLECTION (MULTIPOINT ((1 2), (3 4)), POINT (5 6)) | expected ) but found: ',' |
LINESTRING ((1 2), (3 4)) (not standard WKT; same helper) | expected ) but found: ','; LINESTRING (1 2, (3 4), 5 6) parses |
a polygon with a hole, a MULTILINESTRING | parse (not affected) |
Side note: the helper's own messages "expected: [)] but found: [...]" (:136-137, :142-143) are never produced, because nextCloser throws first for anything other than ) (:347-353).
Exposure and likelihood271 words
- Who is hit. Callers of
SimpleWKTShapeParser.parseorparseExpectedType(public static, checkedParseException) with a standard-form multipoint of two or more points, alone or inside aGEOMETRYCOLLECTION. The class is in an exported core package (module-info.java:38) with no internal or experimental tag. In the Lucene tree only tests call it (TestSimpleWKTShapeParsing,TestTessellator,TestShapeDocValues). - Downstream users (read only, nothing run): rdf4j's Lucene index catches the
ParseException, logs a warning and adds no geo fields, so such a geometry is left out of its geo index and geo queries miss that record; a copy of that class lives in INCEpTION; Anserini's generator does not handle MULTIPOINT anyway. No use was found in Elasticsearch, OpenSearch or Solr (code search, not exhaustive). - Who writes the standard form. PostGIS's ISO WKT writer since PR #682 ("MultiPoint WKT writer: use parentheses in sub-members in WKT_ISO format", merged 2022-04-12). Elasticsearch #151876 (open) says JTS writes
MULTIPOINT ((0 0),(1 1), ...)and that it "can happen in the wild"; Elasticsearch #138162 (open) asks for the same form. - Default, no setting. The parser has no options; assertions on or off make no difference.
- What happens. The first such multipoint is refused with a checked
ParseException: no wrong or partial shape, no hang (a 200,000-point standard-form input fails in 0.2 ms, at the first,), nothing written. - Scale and likelihood. Two points are enough. Multipoints are a minority geometry type and no user report turned up in the recorded searches, so it is rated plausible, low priority.
- Workaround. Rewrite to the bare form,
MULTIPOINT (1 2, 3 4), before parsing.
One JUnit test on the public API: parse MULTIPOINT ((1 2), (3 4)) and compare the result with {{1, 2}, {3, 4}}. Deterministic, single thread, about a second.
/*
* 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 java.text.ParseException;
import org.apache.lucene.tests.util.LuceneTestCase;
public class TestWKTMultiPointParenthesizedPoints extends LuceneTestCase {
public void testTwoParenthesizedPoints() throws Exception {
String wkt = "MULTIPOINT ((1 2), (3 4))";
Object shape;
try {
shape = SimpleWKTShapeParser.parse(wkt);
} catch (ParseException e) {
throw new AssertionError("expected " + wkt + " to parse as {{1, 2}, {3, 4}}, got " + e, e);
}
assertArrayEquals(new double[][] {{1, 2}, {3, 4}}, (double[][]) shape);
}
}
cp TestWKTMultiPointParenthesizedPoints.java $LUCENE/lucene/core/src/test/org/apache/lucene/geo/
cd $LUCENE && ./gradlew :lucene:core:test --tests org.apache.lucene.geo.TestWKTMultiPointParenthesizedPoints -Ptests.seed=DEADBEEF
# assertions off: add -Ptests.asserts=false (same result)
- Expected
MULTIPOINT ((1 2), (3 4))parses todouble[][] {{1, 2}, {3, 4}}.- Observed
java.text.ParseException: expected ) but found: ','fromparseCoordinates(line 141), with assertions on and off.
More files, environment, full output209 words
Pure string parsing: no index, no randomness (the seed does not matter). The ParseException is rethrown as an AssertionError that states expected and actual, with the original exception as its cause. It compiles against 9.1.0 and later; before 9.1.0 (7.x, 8.x, 9.0.0) import org.apache.lucene.util.LuceneTestCase instead, and before 8.5.0 add the sandbox jar, where the class lived then.
As the regression test: the same test, for example as a new method in TestSimpleWKTShapeParsing.
| lucene | main 645d4fe14a20 (11.0.0-SNAPSHOT); the same test fails on 10.6.0; the hunt's 4-test version fails on the 11 releases it was run on, 7.6.0 to 10.5.2 (the releases in between by reading) |
|---|---|
| jdk | OpenJDK 25.0.3 (build 25.0.3+9-LTS) |
| os | Linux, aarch64 (pure string parsing; no platform dependence expected) |
cp $REPRO/TestWKTMultiPointParenthesizedPoints.java $LUCENE/lucene/core/src/test/org/apache/lucene/geo/
cd $LUCENE && ./gradlew :lucene:core:test --tests org.apache.lucene.geo.TestWKTMultiPointParenthesizedPoints -Ptests.seed=DEADBEEF
# assertions off: add -Ptests.asserts=false
$ ./gradlew :lucene:core:test --tests org.apache.lucene.geo.TestWKTMultiPointParenthesizedPoints -Ptests.seed=DEADBEEF
TestWKTMultiPointParenthesizedPoints > testTwoParenthesizedPoints FAILED
java.lang.AssertionError: expected MULTIPOINT ((1 2), (3 4)) to parse as {{1, 2}, {3, 4}}, got java.text.ParseException: expected ) but found: ','
at __randomizedtesting.SeedInfo.seed([DEADBEEF:76696EB2AFA5FE2B]:0)
at org.apache.lucene.geo.TestWKTMultiPointParenthesizedPoints.testTwoParenthesizedPoints(TestWKTMultiPointParenthesizedPoints.java:30)
Caused by:
java.text.ParseException: expected ) but found: ','
at org.apache.lucene.geo.SimpleWKTShapeParser.nextCloser(SimpleWKTShapeParser.java:352)
at org.apache.lucene.geo.SimpleWKTShapeParser.parseCoordinates(SimpleWKTShapeParser.java:141)
at org.apache.lucene.geo.SimpleWKTShapeParser.parseMultiPoint(SimpleWKTShapeParser.java:167)
at org.apache.lucene.geo.SimpleWKTShapeParser.parseGeometry(SimpleWKTShapeParser.java:90)
at org.apache.lucene.geo.SimpleWKTShapeParser.parseExpectedType(SimpleWKTShapeParser.java:69)
at org.apache.lucene.geo.SimpleWKTShapeParser.parse(SimpleWKTShapeParser.java:51)
at org.apache.lucene.geo.TestWKTMultiPointParenthesizedPoints.testTwoParenthesizedPoints(TestWKTMultiPointParenthesizedPoints.java:28)
ERROR: 1 test has failed:
- org.apache.lucene.geo.TestWKTMultiPointParenthesizedPoints.testTwoParenthesizedPoints (:lucene:core)
BUILD FAILED- Its branches for parenthesized points work for one point and mid-list:
MULTIPOINT ((1 2))andMULTIPOINT (1 2, (3 4), 5 6)parse. - It claims ISO/IEC 13249-3 compliance; that grammar writes each member as
(x y):MULTIPOINT ((10 10), (20 20)). - The Elasticsearch parser it was ported from accepts this form; no Lucene test or doc rules it out.
The full argument304 words
- Contract. Class javadoc (
SimpleWKTShapeParser.java:29-34): "Parses shape geometry represented in WKT format", "complies with OGC® document: 12-063r5 and ISO/IEC 13249-3:2016 standard". The Simple Features grammar, read through the OGC's standalone WKT reference and secondary sources (the ISO text itself was not read), defines<multipoint text>as( <point text> {, <point text>}* )with<point text> ::= ( <point> ), exampleMULTIPOINT ((10 10), (20 20)); one transcription marks the bare form "non-standard but widely used". 12-063r5 is the WKT standard for coordinate reference systems, not geometry, so it is not the argument here. - Code intent. Dedicated
(branches (:126,:132) and)checks (:135-138,:141-144); acceptance by position (one point yes, mid-list yes, first or last no) is not described as intended in any documentation or commit message found. - Origin. The class is a port of Elasticsearch's
GeoWKTParser(same identifiers, same javadoc line). In Elasticsearch v6.4.0,parseCoordinateListchecks the first point's)right after it, before the loop, and reads polygon holes through a separate opener; the port checks it after the loop, which keepsparsePolygonHoleworking and breaks multipoints. Elasticsearch PR #27417 (2017), the ported code, listsMULTIPOINT ((10 40), (40 30), ...)as supported. The Lucene commit gives no reason. - Issue, docs, tests. #9584 (LUCENE-8538) lists
MULTIPOINT -> double[][]with no syntax limit;CHANGES.txt(7.6.0,:7093-7094) reads "Add a Simple WKT Shape Parser for creating Lucene Geometries (Polygon, Line, Rectangle) from WKT format", naming those three types and no syntax limit; no javadoc,CHANGES.txtorMIGRATE.mdentry rules the form out.TestSimpleWKTShapeParsing.testMultiPoint(:45-60) tests only the bare form. In-tree precedent (as the cross-model reviewer pointed out):TestGeo3d(:42) givesMULTIPOINT ((10 40), (40 30), (20 20), (30 10))to spatial4j's WKT reader and expects it to parse.
The devil's-advocate check132 words
Strongest case for intended: the class is "Simple"; the introducing issue calls it "useful for simple tests, but also helps for benchmarking from real world data"; it has no in-tree production caller; it supports only a WKT subset (no Z/M tags, no TIN); its compliance claim links a CRS standard; its one MULTIPOINT test uses the bare form; and it fails loudly. So the parenthesized form would be an enhancement.
Why it fails: that covers features the parser never tried to support. Here it does try: explicit branches for parenthesized points that work for one point and in mid-list and fail only at the ends of a list, while the original it was ported from handles the form and the API is public and untagged. It limits severity (low priority), not validity.
Recommended B. MULTIPOINT reads each point bare or in its own parentheses
Let parseMultiPoint read each point itself, bare or as ( point ), in one do/while loop (+10/-1 lines); lines and polygons stay untouched. With it the repro passes with assertions on and off, and all org.apache.lucene.geo tests plus TestShapeDocValues pass.
ArrayList<Double> lats = new ArrayList<>();
ArrayList<Double> lons = new ArrayList<>();
- parseCoordinates(stream, lats, lons);
+ do {
+ // each point is written either bare, "1 2", or in its own parentheses, "(1 2)"
+ if (isNumberNext(stream)) {
+ parseCoordinate(stream, lats, lons);
+ } else {
+ nextOpener(stream);
+ parseCoordinate(stream, lats, lons);
+ nextCloser(stream);
+ }
+ } while (nextCloserOrComma(stream).equals(COMMA));All options and trade-offs2 options · 546 words
Two options were run through gradle in a scratch worktree of 645d4fe14a20; B is recommended because it changes nothing outside MULTIPOINT. Moving only the first point's ) check before the loop, as in the Elasticsearch original, would break every polygon hole unless parsePolygonHole also reads its own (, which is option A.
B. MULTIPOINT reads each point bare or in its own parentheses (validated by the checks) (recommended)
In SimpleWKTShapeParser.parseMultiPoint (:159-173), replace the call to the shared parseCoordinates (:167) with its own loop.
ArrayList<Double> lats = new ArrayList<>();
ArrayList<Double> lons = new ArrayList<>();
- parseCoordinates(stream, lats, lons);
+ do {
+ // each point is written either bare, "1 2", or in its own parentheses, "(1 2)"
+ if (isNumberNext(stream)) {
+ parseCoordinate(stream, lats, lons);
+ } else {
+ nextOpener(stream);
+ parseCoordinate(stream, lats, lons);
+ nextCloser(stream);
+ }
+ } while (nextCloserOrComma(stream).equals(COMMA));Trade-offs
- Accepts the standard form, the bare form and mixed lists (which the current in-loop branch already means to accept), with 3D points and inside collections.
- LINESTRING, polygon and hole parsing are unchanged (checked:
LINESTRING (1 2, (3 4), 5 6)still parses,LINESTRING ((1 2), (3 4))is still refused; neither is standard WKT). - Rejects one malformed multipoint that main accepts,
MULTIPOINT ((1 2, 3 4)(unbalanced; main reads two points), withexpected ) but found: ','. - Leaves the shared helper's quirk in place for other types. Still one linear pass over the tokens.
- Covers non-empty point texts only: point-level
EMPTYmembers, legal in the grammar (MULTIPOINT ((1 2), EMPTY),MULTIPOINT (EMPTY)), are rejected on main and with A or B alike (invalid number found: EMPTY, becauseisNumberNexttreats any word as a number); supporting them is a separate decision.
What the checks validated
Gradle, scratch worktree of 645d4fe14a20 (2026-11-01): the repro fails on the stock tree and passes with B, with assertions on and off; all 17 org.apache.lucene.geo test classes plus TestShapeDocValues pass (200 tests, 0 failures, 7 skipped, incl. TestSimpleWKTShapeParsing and TestTessellator); :lucene:core:check -x test passes. With B shadowed over the main jars, all 104,760 well-formed inputs of a 200,000-input differential probe parse to exactly the written shape.
parseCoordinates reads every coordinate through a new parseBareOrParenthesizedCoordinate (bare, or nextOpener + coordinate + nextCloser), and parsePolygonHole reads the hole's own ( first (the finder's sketch; +14/-16 lines).
Trade-offs
- One rule for every coordinate list; holes no longer rely on the quirk.
- Changes other types:
LINESTRING ((1 2), (3 4))is then accepted (not standard WKT), and malformed strings main accepts are refused, such asLINESTRING ((1 2, 3 4)and a polygon hole without its own((expected ( but found: 2). In the study's 200,000-input probe, accepted inputs with unbalanced parentheses went from 1,277-1,338 per run to 0. - Stricter is arguably better, but it is a behaviour change outside MULTIPOINT for maintainers to decide on.
What the checks validated
Gradle, same worktree: the repro passes with A, with assertions on and off; the same 18 classes pass (200 tests, 0 failures, 7 skipped). Earlier, with the sketch shadowed: the hunt tests pass and the differential probe finds no well-formed input rejected or misread.
rdf4j's Lucene index (read, not run) catches this exception and logs a warning, so a standard-form multipoint is left out of its geo index and geo queries miss that record.
Non-formal: a differential parsing probe fed every WKT geometry type (empty, envelope, 3D, collections) to the parser and compared each result with the shape as written. Two parenthesized points failed; tracing the token reads in parseCoordinates gave the cause, and a public-API JUnit test reproduced it.
The method in full122 words
Method: non-formal (a differential probe, then executed tests). On 2026-10-26 the hunt unit for the LatLon geo queries used, as its secondary method, a probe that parses every WKT geometry type the class supports, including EMPTY, ENVELOPE/BBOX, 3D coordinates and collections, and compares the result with the shape as written. MULTIPOINT ((1 2), (3 4)) was rejected while MULTIPOINT ((1 2)) parsed; following parseCoordinates token by token gave the root cause. A 4-test public-API JUnit (2 controls, 2 bug cases) reproduced it on main (it was later run on 11 releases from 7.6.0 to 10.5.2, 9 by the history step and 2 by the finder); independent checkers then reproduced it with their own tests, and it was cut to the one-test repro.
No prior report found in GitHub issues, PRs and commits (incl. the JIRA archive), open PRs, the Lucene mailing lists, downstream repositories or the web (101 queries, 2026-10-26; refreshed 2026-11-01). Two open Elasticsearch issues ask the same of Elasticsearch's own, different parser.
Searches5 searches
Closest items, none a report of this bug: #9584 (LUCENE-8538, the introducing issue; lists MULTIPOINT -> double[][] with no syntax limit); Elasticsearch #138162 (open, 2025-11-17) and #151876 (open, 2026-06-22), the same input refused by Elasticsearch's own WellKnownText parser, different code with a different defect (no parenthesized-point branch at all) whose fix would not touch Lucene (the two issues disagree on which form is "OGC"); Elasticsearch PR #27417 (2017), the code this parser was ported from, which lists both forms as supported; PostGIS PR #682 (2022, a writer that switched to this form). Limits: GitHub search reads only the first 50-100 results per query; the open-PR file snapshot was partial for one very large PR; private reports are not visible.
| Where | When | How | Found |
|---|---|---|---|
| apache/lucene issues and PRs (incl. the migrated JIRA issues) | 2026-10-26 | 30 scripted prior-report queries plus 9 manual ones: class, method and symptom names, WKT, MULTIPOINT, parentheses, the error text, LUCENE-8538; all 33 WKT and 4 MULTIPOINT hits read | no report; #9584 is the introducing issue |
| Commits and open PRs | 2026-10-26 | GitHub commit search, git log -S isOpenParen, git log -L parseCoordinates; changed files of all 406 open PRs | logic unchanged since 2018; no open PR touches the file |
| Mailing lists and JIRA archive | 2026-10-26 | dev@, java-user@, issues@, commits@ (31 queries, with control queries that do hit); 4 JIRA queries | only the 2018 commit, clean-up commits and unrelated spatial threads |
| Elasticsearch, OpenSearch, Solr; the web | 2026-10-26 | 14 repository queries; 7 web queries | Elasticsearch #138162 and #151876 (their own parser); nothing in OpenSearch or Solr |
| apache/lucene issues and PRs (refresh) | 2026-11-01 | SimpleWKTShapeParser; MULTIPOINT WKT; "expected ) but found"; WKT created since 2026-10-25 | nothing new (#9584, #1017, #9595, #5709 and six unrelated hits) |
Born with the class in LUCENE-8538 (bd714ca1edc, 2018-10-24; backport 5e7858041b8 shipped in 7.6.0). Every release from 7.6.0 to 10.6.0 and main fails; 7.5.0 has no such class. The file is unchanged on upstream main as of 2026-10-30.
Versions tested6 rows
Introduced by LUCENE-8538 "Add Simple WKT Shape Parser" (#9584, by Nick Knize, @nknize; a JIRA patch, no pull request): main-line commit bd714ca1edc (committed 2018-10-24 in lucene/sandbox, first released in 8.0.0) and the same-day branch_7x backport 5e7858041b8 (byte-identical parser file, released in 7.6.0). git blame -w -M -C -C gives bd714ca1edc for every branch and check of parseCoordinates; its signature and two throw statements blame to the LUCENE-9570 reformat 2d6ad2fee6d, which only re-wrapped them, and later commits only clean up. LUCENE-8621 moved the class from lucene-sandbox to lucene-core for 8.5.0 without logic changes. The newest commit touching the file on upstream main, 448c7d8e241 (2026-02-11), predates 645d4fe14a20.
| Version or commit | Verdict | Result |
|---|---|---|
main 645d4fe14a20 | affected | minimal repro fails with assertions on and off (standalone and through Gradle); passes with fix A or B |
| 10.6.0 | affected | minimal repro fails with assertions on and off (release jars) |
main ea57360912ed | affected | the hunt's 4-test JUnit fails 2 of 4 with assertions on and off; both controls pass |
| 8.5.0, 8.11.2, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2 | affected | the 4-test JUnit fails 2 of 4 with assertions on (10.5.2 also off; class in lucene-core) |
| 7.6.0, 7.7.3, 8.0.0, 8.4.1 | first affected 7.6.0 | the 4-test JUnit fails 2 of 4 with assertions on (class in lucene-sandbox) |
| 7.5.0 | no such class | the test does not compile: SimpleWKTShapeParser does not exist |
Confirmed, high confidence: the finder, an independent reproducer, an intended-behaviour devil's advocate and an adjudication all reproduced it on main with assertions on and off. A reviewer from another model family, unable to run code, agreed with the root cause.
Checks and verdicts272 words
Everyone agrees on the root cause and that it is a loud wrong error on valid input, never a wrong shape.
| Check | Verdict | What it found |
|---|---|---|
| finder | candidate | 4-test JUnit fails 2 of 4 on main (assertions on and off; also run on 11 releases with assertions on, 9 by the history step, 7.7.3 and 8.11.2 by the finder); controls pass; 4/4 with the fix sketch, which also passes upstream TestSimpleWKTShapeParsing (14) and TestTessellator (70). |
| cross-model review, refute lens (OpenAI GPT-5.6, read-only) | not real, low (procedural) | Could not run code, so its verdict is procedural; its static trace matched the root cause, it rated the intended argument weak and found the TestGeo3d precedent. Separate re-run: 2 of 4 fail. |
| independent reproducer | real | Its own JUnit fails 2 of 4 on main (assertions on and off), 10.5.2, 9.12.3 and 8.11.2; 4/4 with the fix; its probe matches a static trace and found the END-OF-STREAM variant. |
| cross-model review, intended lens (OpenAI GPT-5.6, read-only) | real, medium | The works-as-designed case is weak. |
| intended-behaviour devil's advocate | real, low severity | Built the strongest intended case and rejected it (position-dependent acceptance; the Elasticsearch original checks the first ) before its loop); main and 10.5.2 give identical output. |
| adjudication | confirmed, high | Re-ran the reproducer's JUnit with a fresh seed (2 of 4 fail, assertions on and off) and the finder's. |
| Registration check, audit and this doc | confirmed | Fresh-seed re-runs fail on main and 10.5.2 and pass with the fix; the one-test repro fails on 645d4fe14a20 and 10.6.0 and through gradle; fixes A and B validated through gradle. |
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 467 KB
Finding record, Lucene and upstream sources, repro files and diagram notes1,358 words
- Registry title
- SimpleWKTShapeParser rejects a MULTIPOINT whose points each sit in their own parentheses, e.g. MULTIPOINT ((1 2), (3 4)), with ParseException "expected ) but found: ','", although it accepts MULTIPOINT ((1 2)) and MULTIPOINT (1 2, 3 4) (every release since 7.6.0)
- Area
- core / geo WKT parsing (SimpleWKTShapeParser)
- Priority
- Low, rank 1070: The public core SimpleWKTShapeParser loudly rejects the standard parenthesized MULTIPOINT form with an easy workaround and known downstream users.
- Impact class
- wrong error
- Impact note
- Hits applications that call SimpleWKTShapeParser.parse or parseExpectedType (a public class in the exported core package org.apache.lucene.geo with no @lucene.internal or @lucene.experimental tag; only tests call it in the Lucene tree, and a read-only code search found downstream users such as rdf4j's Lucene index and its copy in INCEpTION, plus Anserini, whose generator does not handle MULTIPOINT) with MULTIPOINT text in the standard form, where each point has its own parentheses: the geometry, or the GEOMETRYCOLLECTION that holds it, is rejected with a checked ParseException; no setting is involved, no wrong shape is ever returned, nothing hangs, no index is touched and no scale is needed, with -ea and -da, in every release from 7.6.0 to 10.6.0 (10.6.0 executed with the minimal repro) and on main; the bare form MULTIPOINT (1 2, 3 4) still parses, so callers who control the text have a workaround.
- Summary
- You call SimpleWKTShapeParser.parse("MULTIPOINT ((1 2), (3 4))"), the Simple Features WKT form in which each point of a multipoint sits in its own parentheses, and expect double[][] {{1, 2}, {3, 4}}, because the class says it parses WKT in compliance with ISO/IEC 13249-3:2016 and it already accepts MULTIPOINT ((1 2)) and the bare legacy form MULTIPOINT (1 2, 3 4). Instead it throws java.text.ParseException "expected ) but found: ','", also inside a GEOMETRYCOLLECTION and with 3D points. The cause is in parseCoordinates: when the first point starts with '(' the code before the loop does not read that point's ')', so the loop takes that ')' as the end of the whole list and the check after the loop then finds the ',' where it wants the missing ')'; whether a parenthesized point is accepted therefore depends on where it sits (MULTIPOINT (1 2, (3 4), 5 6) parses, while MULTIPOINT (1 2, (3 4)) fails with 'expected word but found: END-OF-STREAM').
- Introduced
- bd714ca1edc681cd871e6d385bd98a9cb9d7ffe6 (main line, LUCENE-8538 'Add Simple WKT Shape Parser' by @nknize, migrated as apache/lucene#9584; JIRA patch era, no pull request; authored 2018-10-22, committed 2018-10-24 to master in lucene/sandbox; first shipped in 8.0.0) and its branch_7x backport 5e7858041b87fc9091b0aae7c228445ab6566231 (same day, byte-identical parser file; shipped in 7.6.0). The faulty order was born with the class and is not a regression [read: git blame -w -M -C -C on main lines 121-145 gives bd714ca1edc for every logic line, and 2d6ad2fee6d (LUCENE-9570 reformat) only re-wrapped the two throws; a whitespace-insensitive diff from bd714ca1edc to main touches nothing in parseCoordinates, parseMultiPoint, nextCloser, nextCloserOrComma, nextWord or isNumberNext; exec: 7.5.0 has no class (compile error), 7.6.0 fails] (2018-10-24)
- First affected version
- 7.6.0 [exec] (branch_7x backport; the class lived in lucene-sandbox from 7.6.0 to 8.4.1 and in lucene-core from 8.5.0, moved by LUCENE-8621 without logic changes); first main-line release 8.0.0 [exec]; 7.5.0 has no SimpleWKTShapeParser
- Affected versions
- every release from 7.6.0 through 10.6.0, and main ea57360912ed; the one-test minimal repro fails on the 10.6.0 release jars (SHA-1 checked against Maven Central) with -ea and -da [exec 2026-11-01]. [exec] the finder's JUnit (2 controls, 2 bug tests) fails 2 of 4 with both controls passing on 7.6.0 (junitcore runner, because 7.6.0 pins JUnit 4.10), 7.7.3, 8.0.0, 8.4.1 (last sandbox release), 8.5.0 (first in core), 8.11.2, 8.11.4, 9.0.0, 9.12.3, 10.0.0, 10.5.2 and main (-ea and -da); the independent reproduction's JUnit fails 2 of 4 on main (-ea and -da), 10.5.2, 9.12.3 and 8.11.2 (sandbox jar); re-runs on main -ea and -da and 10.5.2 -da. [read] the logic is unchanged since the introducing commit, so the releases not executed are affected by inference; branch_10_6 (released as 10.6.0 on 2026-10-29) and the unreleased branch_10x carry it. Not fixed upstream: the file has no diff between ea57360912ed and upstream main a9779ef3c19 (2026-10-26). aarch64 with JDK 25 only (pure string parsing)
- Status
- documented (brief, checks and a per-finding doc exist)
- Verification
- Confirmed by AI-run checks: an independent repro, a cross-model review that could only read the code (its low-confidence “not real” is procedural), an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
- Found by
- non-formal
- Upstream and prior reports
- none found (2026-10-26, read-only): a scripted prior-report search (30 GitHub issue/PR queries on apache/lucene, 3 commit, 4 downstream-repository, 20 mailing-list over dev@, java-user@, issues@ and commits@, 4 JIRA-archive, and web) plus 34 manual gh, list, git and web queries; the finder's run (7 queries) and the intended-behaviour check's (20) agree. Nothing reports or discusses SimpleWKTShapeParser rejecting MULTIPOINT points in parentheses, and no open PR touches the file. Closest items, not reports of this bug: #9584 (LUCENE-8538), the introducing issue, which lists 'MULTIPOINT -> double[][]' with no syntax limit; Elasticsearch #138162 (open, 2025-11-17, >bug) and #151876 (open, 2026-06-22, >tech-debt), the same standard MULTIPOINT form rejected by Elasticsearch's own WellKnownText parser, different code with a different defect (no parenthesized-point branch at all) whose fix would not touch Lucene; Elasticsearch PR #27417 (merged 2017-12-05), the GeoWKTParser this class was ported from, whose description lists both MULTIPOINT forms as supported. Search limits: GitHub search reads only the first 50-100 results per query; the open-PR file snapshot is partial for one very large PR; one Q&A page was seen as a snippet only; private channels were not searched
- Lucene commit tested
- main
645d4fe14a20(the minimal repro with assertions on and off, standalone and through gradle; fixes A and B in a scratch worktree); the 10.6.0 release jars (assertions on and off); the hunt's 4-test JUnit, with assertions on, on 7.6.0, 7.7.3, 8.0.0, 8.4.1, 8.5.0, 8.11.2, 8.11.4, 9.0.0, 9.12.3, 10.0.0 and 10.5.2 (10.5.2 also with assertions off) and, with assertions on and off, on mainea57360912ed, whose parser file is identical to645d4fe14a20's; 7.5.0 has no such class. Upstream main2f424fd428da(2026-10-30) has the same file (read).
- Finding record
lucene/core/src/java/org/apache/lucene/geo/SimpleWKTShapeParser.java:29-34 (at ea57360912ed) (class javadoc: 'Parses shape geometry represented in WKT format', 'complies with OGC document: 12-063r5 and ISO/IEC 13249-3:2016'), :35 (public class, no internal/experimental tag), :50-55 (public static parse and parseExpectedType, checked ParseException), :122-145 (parseCoordinates: first coordinate at :125-128 without its ')', loop condition nextCloserOrComma at :130, in-loop '(' branch with its own ')' check at :131-139, check after the loop at :141-144), :159-173 (parseMultiPoint delegates to parseCoordinates), :203-210 (parsePolygonHole relies on the first-position '(' branch), :347-353 (nextCloser throws "expected ) but found"), :374 (nextCloserOrComma)lucene/core/src/java/module-info.java:38 (at ea57360912ed) (exports org.apache.lucene.geo); lucene/core/src/test/org/apache/lucene/geo/TestSimpleWKTShapeParsing.java:45-48 (testMultiPoint uses only the bare form); lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/TestGeo3d.java:42 (the parenthesized form given to a different WKT reader); grep of lucene/*/src/java: no non-test callerSECURITY.md lines 28, 38, 42, 46, 50 (threat model)a Lucene clone origin/main a9779ef3c19 (2026-10-26): git diff ea57360912ed origin/main on SimpleWKTShapeParser.java is empty
- The finding in 30 seconds
lucene/core/src/java/org/apache/lucene/geo/SimpleWKTShapeParser.java:50-55, 78-90, 122-145, 148-156, 159-173, 203-210, 282-295, 347-353, 374-383 (at 645d4fe14a20)
- Who is affected
lucene/core/src/java/org/apache/lucene/geo/SimpleWKTShapeParser.java:35, 50-55 (at 645d4fe14a20); lucene/core/src/java/module-info.java:38grep of lucene/*/src at 645d4fe14a20: SimpleWKTShapeParser is used only by TestSimpleWKTShapeParsing, TestTessellator and TestShapeDocValuesgithub.com/postgis/postgis/pull/682 (title, merged 2022-04-12; read-only GitHub API, 2026-11-01)github.com/elastic/elasticsearch/issues/151876 and /issues/138162 (state, dates, body; read-only GitHub API, 2026-11-01)
- Why it looks unintended
lucene/core/src/java/org/apache/lucene/geo/SimpleWKTShapeParser.java:29-34, 126, 131-138, 141-144, 203-210 (at 645d4fe14a20)lucene/core/src/test/org/apache/lucene/geo/TestSimpleWKTShapeParsing.java:45-60 (at 645d4fe14a20)lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/TestGeo3d.java:42 (at 645d4fe14a20)lucene/CHANGES.txt:7093-7094 (7.6.0 section, LUCENE-8538)
- Prior reports
read-only GitHub search API, 2026-11-01 (4 queries on apache/lucene)
- History
read-only GitHub API, 2026-11-01: commits touching the file on apache/lucene main (newest 448c7d8e241); main at 2f424fd428da
- TestWKTMultiPointParenthesizedPoints.java (JUnit, one test):
repro/LFV-570/src/org/apache/lucene/geo/TestWKTMultiPointParenthesizedPoints.java(34 lines)
Made in parallel with this doc from the code at 645d4fe14a20 and the repro's input and output.
assets/LFV-570/bug.png(467 KB)
