elastic/elasticsearch · error · IllegalArgumentException
LinearRing cannot have first and last points differ:
Error message
LinearRing cannot have first and last points differ:
What it means
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.
Source
Thrown at libs/geo/src/main/java/org/elasticsearch/geometry/simplify/StreamingGeometrySimplifier.java:361
}
}
static LinearRing produceLinearRing(StreamingGeometrySimplifier<?> simplifier) {
if (simplifier.length < 1) {
throw new IllegalArgumentException("No points have been consumed");
}
if (simplifier.length < 4) {
throw new IllegalArgumentException("LinearRing cannot have less than 4 points");
}
double[] x = new double[simplifier.length];
double[] y = new double[simplifier.length];
for (int i = 0; i < simplifier.length; i++) {
x[i] = simplifier.points[i].x;
y[i] = simplifier.points[i].y;
}
if (x[simplifier.length - 1] != x[0] || y[simplifier.length - 1] != y[0]) {
String inequality = "(" + x[0] + " " + y[0] + ") != (" + x[simplifier.length - 1] + " " + y[simplifier.length - 1] + ")";
throw new IllegalArgumentException("LinearRing cannot have first and last points differ: " + inequality);
}
return new LinearRing(x, y);
}
}
View on GitHub (pinned to db6a809a66)
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.
Example fix
// before for (int i = 0; i < ring.size(); i++) s.consume(ring.getX(i), ring.getY(i)); Polygon p = s.produce(); // throws if ring is open // after for (int i = 0; i < ring.size(); i++) s.consume(ring.getX(i), ring.getY(i)); s.consume(ring.getX(0), ring.getY(0)); // close the ring Polygon p = s.produce();
Defensive patterns
Strategy: validation
Validate before calling
// ensure closure before consuming for (int i = 0; i < ring.size(); i++) s.consume(ring.getX(i), ring.getY(i)); s.consume(ring.getX(0), ring.getY(0)); // close the ring
Type guard
static boolean isClosedRing(double[] xs, double[] ys) {
return xs.length >= 4 && xs[0] == xs[xs.length - 1] && ys[0] == ys[ys.length - 1];
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- LinearRing cannot have less than 4 points
- No points have been consumed
- Unsupported geometry type:
- Unknown simplification error calculator:
- circle [
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/d20810fee219f10a.
Report an issue: GitHub.