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

  1. Close the ring before consuming: after the loop, consume the first point again so first == last.
  2. If the source ring is already closed but drift exists, snap the last point to the first before consuming.
  3. 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

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


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/d20810fee219f10a. Report an issue: GitHub.