openzipkin/zipkin · error · IllegalArgumentException

%s reading %s from TBinary

Error message

%s reading %s from TBinary

What it means

Zipkin's legacy thrift codec wraps decode failures as IllegalArgumentException('<cause> reading <type> from TBinary'), normalizing EOFException to 'EOF' and IllegalStateException/BufferUnderflowException to 'Malformed'. It surfaces when Scribe/legacy storage data or thrift-encoded spans cannot be parsed with the zipkin ThriftLayout.

Source

Thrown at zipkin/src/main/java/zipkin2/internal/ThriftCodec.java:115

    return buffer.readInt();
  }

  static <T> void writeList(WriteBuffer.Writer<T> writer, List<T> value, WriteBuffer buffer) {
    int length = value.size();
    writeListBegin(buffer, length);
    for (int i = 0; i < length; i++) {
      writer.write(value.get(i), buffer);
    }
  }

  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);

View on GitHub (pinned to 878ce2a1fa)

Solutions

  1. Read getCause() — EOF means truncation, Malformed means structural mismatch; match your fix accordingly.
  2. Confirm the payload came from zipkin's thrift schema (finagle zipkin tracer, zipkin-thrift definitions), not an unrelated thrift service.
  3. For truncated rows, check storage write limits and re-ingest the trace from the source.
  4. During migrations, dispatch on Content-Type/prefix: JSON starts with '{'/'[', proto3 with 0x0a, thrift begins with a struct field header.

Example fix

// before
Span span = SpanBytesDecoder.THRIFT.decodeOne(bytes);

// after
Span span;
if (bytes.length > 0 && (bytes[0] == '{' || bytes[0] == '[')) {
  span = SpanBytesDecoder.JSON_V2.decodeOne(bytes);
} else {
  span = SpanBytesDecoder.THRIFT.decodeOne(bytes);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// dispatch on payload shape before choosing a decoder
SpanBytesDecoder pickDecoder(byte[] b) {
  if (b.length == 0) throw new IllegalArgumentException("empty payload");
  char c = (char)(b[0] & 0xff);
  if (c == '{' || c == '[') return SpanBytesDecoder.JSON_V2;
  if (c == 0x0a || c == 0x0c) return SpanBytesDecoder.PROTO3;
  return SpanBytesDecoder.THRIFT;
}

Try / catch

try { return SpanBytesDecoder.THRIFT.decodeOne(bytes); } catch (IllegalArgumentException e) { LOG.warn("legacy thrift unreadable: {}", e.getMessage()); return null; }

Prevention

When it happens

Trigger: Calling SpanBytesDecoder.THRIFT.decode/decodeList with bytes that are not the fixed zipkin thrift span layout: wrong field types, truncated structs, or bytes from a different thrift schema.

Common situations: Migrating old scribe-collected spans; reading legacy Cassandra rows; a finagle version emitting a revised thrift schema; feeding JSON/proto bytes to a thrift decoder.

Related errors


AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14). Data as JSON: /api/errors/aa4b7c6a41ced2e7. Report an issue: GitHub.