{"record":{"id":"d20810fee219f10a","repo":"elastic/elasticsearch","slug":"linearring-cannot-have-first-and-last-points-diffe","errorCode":null,"errorMessage":"LinearRing cannot have first and last points differ: ","messagePattern":"LinearRing cannot have first and last points differ: ","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"libs/geo/src/main/java/org/elasticsearch/geometry/simplify/StreamingGeometrySimplifier.java","lineNumber":361,"sourceCode":"        }\n    }\n\n    static LinearRing produceLinearRing(StreamingGeometrySimplifier<?> simplifier) {\n        if (simplifier.length < 1) {\n            throw new IllegalArgumentException(\"No points have been consumed\");\n        }\n        if (simplifier.length < 4) {\n            throw new IllegalArgumentException(\"LinearRing cannot have less than 4 points\");\n        }\n        double[] x = new double[simplifier.length];\n        double[] y = new double[simplifier.length];\n        for (int i = 0; i < simplifier.length; i++) {\n            x[i] = simplifier.points[i].x;\n            y[i] = simplifier.points[i].y;\n        }\n        if (x[simplifier.length - 1] != x[0] || y[simplifier.length - 1] != y[0]) {\n            String inequality = \"(\" + x[0] + \" \" + y[0] + \") != (\" + x[simplifier.length - 1] + \" \" + y[simplifier.length - 1] + \")\";\n            throw new IllegalArgumentException(\"LinearRing cannot have first and last points differ: \" + inequality);\n        }\n        return new LinearRing(x, y);\n    }\n}\n","sourceCodeStart":343,"sourceCodeEnd":366,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/geo/src/main/java/org/elasticsearch/geometry/simplify/StreamingGeometrySimplifier.java#L343-L366","documentation":"Thrown by StreamingGeometrySimplifier.produceLinearRing when the accumulated points do not satisfy the LinearRing closure requirement: the first point (x[0], y[0]) must equal the last point (x[length-1], y[length-1]). The message embeds both coordinates so the mismatch is visible.","triggerScenarios":"Consuming a sequence of points where the closing vertex was omitted or differs from the opening vertex, then calling produce(). For example, feeding a polygon's distinct corners [A, B, C] without repeating A as the fourth point.","commonSituations":"Source data representing polygons as open vertex lists (no closing duplicate) while the simplifier expects closed rings; floating-point drift where the closing point is 'almost' equal but not bit-identical; a code path that appends vertices from a GeoJSON array without re-adding the first coordinate.","solutions":["Close the ring before consuming: after the loop, consume the first point again so first == last.","If the source ring is already closed but drift exists, snap the last point to the first before consuming.","Validate ring closure on the source geometry and reject or repair unclosed rings upstream."],"exampleFix":"// before\nfor (int i = 0; i < ring.size(); i++) s.consume(ring.getX(i), ring.getY(i));\nPolygon p = s.produce(); // throws if ring is open\n\n// after\nfor (int i = 0; i < ring.size(); i++) s.consume(ring.getX(i), ring.getY(i));\ns.consume(ring.getX(0), ring.getY(0)); // close the ring\nPolygon p = s.produce();","handlingStrategy":"validation","validationCode":"// ensure closure before consuming\nfor (int i = 0; i < ring.size(); i++) s.consume(ring.getX(i), ring.getY(i));\ns.consume(ring.getX(0), ring.getY(0)); // close the ring","typeGuard":"static boolean isClosedRing(double[] xs, double[] ys) {\n    return xs.length >= 4 && xs[0] == xs[xs.length - 1] && ys[0] == ys[ys.length - 1];\n}","tryCatchPattern":null,"preventionTips":["Always re-consume the first point as the last point to close the ring before produce().","Snap the closing vertex to the opening vertex bit-exactly to avoid float drift.","Validate ring closure on source polygons at ingestion."],"tags":["geometry","simplification","streaming","polygon","linear-ring","closure","libs-geo"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}