elastic/elasticsearch · error · ParseException

maximum nested depth of

Error message

maximum nested depth of 

What it means

writeWKBGeometryCollection (line 460) guards recursion depth: at line 474 it throws ParseException if depth >= WellKnownText.MAX_NESTED_DEPTH (1000, defined at WellKnownText.java:49). Each nested GEOMETRYCOLLECTION increments depth (passed as depth+1 at lines 479/485). This is a denial-of-service safeguard against pathologically or maliciously nested input that would otherwise unbounded-recursion the parser.

Source

Thrown at libs/geo/src/main/java/org/elasticsearch/geometry/utils/WellKnownBinary.java:475

        }
    }

    private static void writeWKBGeometryCollection(
        StreamTokenizer stream,
        ByteArrayOutputStream out,
        ByteBuffer scratch,
        boolean coerce,
        int depth,
        boolean explicitZ,
        GeometryValidator validator
    ) throws IOException, ParseException {
        if (WellKnownText.nextEmptyOrOpen(stream).equals(WellKnownText.EMPTY)) {
            writeInt(out, scratch, 7);
            writeInt(out, scratch, 0);
            return;
        }
        if (depth >= WellKnownText.MAX_NESTED_DEPTH) {
            throw new ParseException("maximum nested depth of " + WellKnownText.MAX_NESTED_DEPTH + " exceeded", stream.lineno());
        }
        List<byte[]> subGeometries = new ArrayList<>();
        ByteArrayOutputStream subOut = new ByteArrayOutputStream();
        writeWKBGeometry(stream, subOut, scratch, coerce, depth + 1, validator);
        byte[] subBytes = subOut.toByteArray();
        subGeometries.add(subBytes);
        boolean hasZ = wkbTypeHasZ(subBytes);
        while (WellKnownText.nextCloserOrComma(stream).equals(WellKnownText.COMMA)) {
            subOut = new ByteArrayOutputStream();
            writeWKBGeometry(stream, subOut, scratch, coerce, depth + 1, validator);
            subBytes = subOut.toByteArray();
            subGeometries.add(subBytes);
            if (wkbTypeHasZ(subBytes) != hasZ) {
                throw new IllegalArgumentException("all elements of the collection should have the same number of dimension");
            }
        }
        WellKnownText.checkZorMAttribute(explicitZ, hasZ);
        writeInt(out, scratch, hasZ ? 1007 : 7);

View on GitHub (pinned to db6a809a66)

Solutions

  1. Apply a much smaller client-side depth limit (e.g. 20-50) on untrusted WKT before passing to fromWKT, to fail fast and free the parser from the heavy work.
  2. Validate/cap GEOMETRYCOLLECTION nesting in your ingestion schema or ingest processor.
  3. If legitimately deep nesting is expected (rare), pre-flatten the collection before serialization.
  4. Catch ParseException and surface a 400 to the client rather than crashing the ingest path.

Example fix

// before
byte[] wkb = WellKnownBinary.fromWKT(deeplyNestedWkt, BO, false, v); // throws at depth 1000

// after — pre-check depth cheaply
if (countNesting(wkt, "GEOMETRYCOLLECTION") > 50) {
    throw new IllegalArgumentException("nesting too deep");
}
byte[] wkb = WellKnownBinary.fromWKT(wkt, BO, false, v);
Defensive patterns

Strategy: validation

Validate before calling

static final int CLIENT_MAX_DEPTH = 50;
int countCollectionNesting(String wkt) {
    int d = 0, max = 0;
    for (int i = 0; i < wkt.length(); i++) {
        if (wkt.regionMatches(true, i, "GEOMETRYCOLLECTION", 0, 18)) { d++; max = Math.max(max, d); }
        // crude: decrement on ')' is unreliable; better to parse
    }
    return max;
}
boolean depthWithinBudget(String wkt) { return countCollectionNesting(wkt) <= CLIENT_MAX_DEPTH; }

Type guard

// no type guard — operates on raw WKT string before parsing

Try / catch

try {
    return WellKnownBinary.fromWKT(wkt, bo, coerce, v);
} catch (ParseException e) {
    if (e.getMessage().startsWith("maximum nested depth")) {
        throw new IllegalArgumentException("input nesting exceeds client budget", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling WellKnownBinary.fromWKT on a WKT string with GEOMETRYCOLLECTION nesting deeper than 1000 levels, e.g. 1001 layers of 'GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(...(POINT(0 0))...))'. Generated/malicious input is the typical source.

Common situations: Accepting untrusted WKT input without a depth sanity check (web-facing ingestion endpoints). Bugs in data-generation code that recursively wraps collections. Adversarial payloads aimed at stack-overflowing the parser. Extremely unlikely from normal GIS data, where nesting rarely exceeds 2-3 levels.

Related errors


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