elastic/elasticsearch · error · IllegalArgumentException

When specifying 'Z' or 'M', coordinates must include three…

Error message

When specifying 'Z' or 'M', coordinates must include three values. Only two coordinates were provided

What it means

Thrown by WellKnownText.checkZorMAttribute after a geometry is parsed. If the WKT explicitly specified a 'Z' or 'M' qualifier on the type keyword (e.g. "POINT Z (...)" or "POINTM (...)"), but the parsed geometry's hasZ() returns false (coordinates only had 2 values), the qualifier promised a third dimension that was not delivered.

Solutions

  1. Either supply the third coordinate value for every tuple so geometry.hasZ() is true: "POINT Z (1 2 3)".
  2. Or remove the Z/M qualifier if the data is genuinely 2D: "POINT (1 2)".
  3. Ensure consistency: every coordinate tuple in the geometry must have the same dimensionality as the qualifier declares.

Example fix

// before: Z qualifier but only 2 coordinates
Geometry g = WellKnownText.fromWKT("POINT Z (1 2)");

// after: provide the Z value
Geometry g = WellKnownText.fromWKT("POINT Z (1 2 3)");
// or drop the qualifier for 2D data
Geometry g = WellKnownText.fromWKT("POINT (1 2)");
Defensive patterns

Strategy: validation

Validate before calling

// Ensure Z qualifier matches coordinate arity before parsing
static void validateZConsistency(String wkt) {
    String upper = wkt.toUpperCase(Locale.ROOT);
    boolean hasQualifier = upper.contains(" Z") || upper.contains(" Z(") || upper.contains("M(") || upper.matches("(?i).*\\bZ\\b.*\\(.*\\).*");
    if (hasQualifier) {
        // every coordinate tuple must have 3 components; do a quick count
        // (full validation is done by the parser, this is a pre-check)
    }
}

Try / catch

try {
    Geometry g = WellKnownText.fromWKT(wkt);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("Z")) {
        // prompt user to add the third coordinate or drop the qualifier
    }
    throw e;
}

Prevention

When it happens

Trigger: WKT input of the form "POINT Z (1 2)" or "LINESTRING M (0 0, 1 1)" where the qualifier declares Z/M but the coordinate tuples contain only two numbers. The parser reads the Z/M token, sets isExplicitlySpecifiesZorM=true, then parsePoint/parseLine reads only lon and lat (no third number), so geometry.hasZ() is false and checkZorMAttribute throws.

Common situations: Hand-authored or generated WKT where the Z qualifier was added for documentation but coordinates were left 2D; mixed-up serialization from a tool that emits the qualifier header but trims the third coordinate; copy-paste from a 3D dataset with the altitude column dropped.

Related errors


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

Appendix: source

Thrown at libs/geo/src/main/java/org/elasticsearch/geometry/utils/WellKnownText.java:470

            case "point" -> parsePoint(stream);
            case "multipoint" -> parseMultiPoint(stream);
            case "linestring" -> parseLine(stream);
            case "multilinestring" -> parseMultiLine(stream);
            case "polygon" -> parsePolygon(stream, coerce);
            case "multipolygon" -> parseMultiPolygon(stream, coerce);
            case "bbox" -> parseBBox(stream);
            case "geometrycollection" -> parseGeometryCollection(stream, coerce, depth + 1);
            case "circle" -> // Not part of the standard, but we need it for internal serialization
                parseCircle(stream);
            default -> throw new IllegalArgumentException("Unknown geometry type: " + type);
        };
        checkZorMAttribute(isExplicitlySpecifiesZorM, geometry.hasZ());
        return geometry;
    }

    static void checkZorMAttribute(boolean isExplicitlySpecifiesZorM, boolean hasZ) {
        if (isExplicitlySpecifiesZorM && hasZ == false) {
            throw new IllegalArgumentException(
                "When specifying 'Z' or 'M', coordinates must include three values. Only two coordinates were provided"
            );
        }
    }

    private static GeometryCollection<Geometry> parseGeometryCollection(StreamTokenizer stream, boolean coerce, int depth)
        throws IOException, ParseException {
        if (nextEmptyOrOpen(stream).equals(EMPTY)) {
            return GeometryCollection.EMPTY;
        }
        if (depth > MAX_NESTED_DEPTH) {
            throw new ParseException("maximum nested depth of " + MAX_NESTED_DEPTH + " exceeded", stream.lineno());
        }
        List<Geometry> shapes = new ArrayList<>();
        shapes.add(parseGeometry(stream, coerce, depth));
        while (nextCloserOrComma(stream).equals(COMMA)) {
            shapes.add(parseGeometry(stream, coerce, depth));
        }

View on GitHub (pinned to db6a809a66)