openzipkin/zipkin · error · IllegalStateException
Maximum skip depth exceeded
Error message
Maximum skip depth exceeded
What it means
When skipping unknown thrift fields, ThriftCodec.skip recurses for nested structs/lists with a depth budget (MAX_SKIP_DEPTH). Each nesting level decrements it; reaching zero throws IllegalStateException('Maximum skip depth exceeded'). This is a bomb-defusal guard against maliciously deep thrift payloads that would cause stack overflow or unbounded CPU.
Source
Thrown at zipkin/src/main/java/zipkin2/internal/ThriftCodec.java:123
}
}
static IllegalArgumentException exceptionReading(String type, Exception e) {
String cause = e.getMessage() == null ? "Error" : e.getMessage();
if (e instanceof EOFException) cause = "EOF";
if (e instanceof IllegalStateException || e instanceof BufferUnderflowException) {
cause = "Malformed";
}
String message = String.format("%s reading %s from TBinary", cause, type);
throw new IllegalArgumentException(message, e);
}
static void skip(ReadBuffer buffer, byte type) {
skip(buffer, type, MAX_SKIP_DEPTH);
}
static void skip(ReadBuffer buffer, byte type, int maxDepth) {
if (maxDepth <= 0) throw new IllegalStateException("Maximum skip depth exceeded");
switch (type) {
case TYPE_BOOL:
case TYPE_BYTE:
buffer.skip(1);
break;
case TYPE_I16:
buffer.skip(2);
break;
case TYPE_I32:
buffer.skip(4);
break;
case TYPE_DOUBLE:
case TYPE_I64:
buffer.skip(8);
break;
case TYPE_STRING:
buffer.skip(buffer.readInt());
break;View on GitHub (pinned to 878ce2a1fa)
Solutions
- Treat it as malicious or corrupt input: drop the message, do not retry.
- If legitimately deep data is expected (it is not, for zipkin spans), re-encode with the current zipkin thrift schema so no unknown nested fields exist.
- Rate-limit and authenticate public ingest endpoints so fuzzed payloads cannot reach the decoder.
- Log the payload hash to correlate repeated attack/corruption sources.
Example fix
// before
Span span = SpanBytesDecoder.THRIFT.decodeOne(bytes);
// after
Span span;
try {
span = SpanBytesDecoder.THRIFT.decodeOne(bytes);
} catch (IllegalArgumentException | IllegalStateException e) {
LOG.warn("rejecting malformed thrift span ({}), dropping", e.getMessage());
span = null;
} Defensive patterns
Strategy: try-catch
Try / catch
catch (IllegalStateException | IllegalArgumentException e) { quarantine(bytes); LOG.warn("rejected pathological thrift payload: {}", e.getMessage()); } Prevention
- Authenticate and rate-limit public ingest endpoints.
- Treat depth-limit hits as attacks or corruption, never as retryable.
- Keep zipkin-server updated — depth guards harden over releases.
When it happens
Trigger: Decoding a thrift-encoded span containing an unknown list/struct field whose elements are themselves containers, nested deeper than the allowed limit — typical of fuzzed or adversarial input, or corrupt bytes that look like deeply nested containers when mis-skipped.
Common situations: A public scribe/collector endpoint receiving crafted spans; corrupt storage blobs where random bytes decode as nested type codes.
Related errors
- Expected json or thrift object, not list encoding
- Malformed: fieldNumber was zero at byte
- Malformed: invalid wireType
- Malformed: invalid boolean value at byte
- hex field greater than 32 chars long: {}
AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14).
Data as JSON: /api/errors/0951f28dd7226053.
Report an issue: GitHub.