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
- Read getCause() — EOF means truncation, Malformed means structural mismatch; match your fix accordingly.
- Confirm the payload came from zipkin's thrift schema (finagle zipkin tracer, zipkin-thrift definitions), not an unrelated thrift service.
- For truncated rows, check storage write limits and re-ingest the trace from the source.
- 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
- During legacy migration, carry an encoding label alongside bytes.
- Convert old thrift data to V2 JSON/PROTO3 once, then retire the thrift path.
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
- Expected json or thrift object, not list encoding
- %s reading %s from json
- %s reading %s from proto3
- Malformed: fieldNumber was zero at byte
- Truncated: length {} > bytes available {}
AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14).
Data as JSON: /api/errors/aa4b7c6a41ced2e7.
Report an issue: GitHub.