openzipkin/zipkin · error · IllegalArgumentException

Could not detect the span format

Error message

Could not detect the span format

What it means

SpanBytesDecoderDetector.detectDecoder(ByteBuffer) sniffs the format from the first byte: values <= 16 mean binary (PROTO3 if the protobuf3 heuristic matches, else Thrift); '[' or '{' mean JSON (v2 if it contains "endpoints"/"tags" fields, else JSON_V1). Anything else throws IllegalArgumentException('Could not detect the span format'). This underlying exception propagates through both decoderForMessage and decoderForListMessage.

Source

Thrown at zipkin/src/main/java/zipkin2/SpanBytesDecoderDetector.java:90

  public static BytesDecoder<Span> decoderForListMessage(ByteBuffer spans) {
    BytesDecoder<Span> decoder = detectDecoder(spans);
    byte first = spans.get(spans.position());
    if (first != 12 /* List[ThriftSpan] */
        && first != 11 /* openzipkin/zipkin-reporter-java#133 */
        && !protobuf3(spans) && first != '[') {
      throw new IllegalArgumentException("Expected json, proto3 or thrift list encoding");
    }
    return decoder;
  }

  /** @throws IllegalArgumentException if the input isn't a json or thrift list or object. */
  static BytesDecoder<Span> detectDecoder(ByteBuffer bytes) {
    byte first = bytes.get(bytes.position());
    if (first <= 16) { // binary format
      if (protobuf3(bytes)) return SpanBytesDecoder.PROTO3;
      return SpanBytesDecoder.THRIFT; /* the first byte is the TType, in a range 0-16 */
    } else if (first != '[' && first != '{') {
      throw new IllegalArgumentException("Could not detect the span format");
    }
    if (contains(bytes, ENDPOINT_FIELD_SUFFIX)) return SpanBytesDecoder.JSON_V2;
    if (contains(bytes, TAGS_FIELD)) return SpanBytesDecoder.JSON_V2;
    return SpanBytesDecoder.JSON_V1;
  }

  static boolean contains(ByteBuffer bytes, byte[] subsequence) {
    bytes:
    for (int i = 0; i < bytes.remaining() - subsequence.length + 1; i++) {
      for (int j = 0; j < subsequence.length; j++) {
        if (bytes.get(bytes.position() + i + j) != subsequence[j]) {
          continue bytes;
        }
      }
      return true;
    }
    return false;
  }

View on GitHub (pinned to 878ce2a1fa)

Solutions

  1. Decompress according to Content-Encoding before calling the detector.
  2. Reset/duplicate the ByteBuffer (buf.duplicate().position(0)) if it has been read before.
  3. Verify the producer encodes with one of the supported codecs (THRIFT, JSON_V1, JSON_V2, PROTO3 list forms) and match collector/reporter versions.

Example fix

// before
BytesDecoder<Span> d = SpanBytesDecoderDetector.decoderForListMessage(buf);

// after
if ("gzip".equals(contentEncoding)) body = gunzip(body);
BytesDecoder<Span> d = SpanBytesDecoderDetector.decoderForListMessage(ByteBuffer.wrap(body));
Defensive patterns

Strategy: try-catch

Validate before calling

boolean looksLikeSpanPayload(byte[] p) {
  if (p.length == 0) return false;
  byte f = p[0];
  return f <= 16 || f == '[' || f == '{';
}

Try / catch

try {
  decoder = SpanBytesDecoderDetector.decoderForListMessage(buf);
} catch (IllegalArgumentException e) {
  log.warn("undecodable payload, first byte={}", buf.get(buf.position()));
  metrics.incrementMalformed(); // drop, don't crash the consumer
}

Prevention

When it happens

Trigger: Passing payloads whose first byte is > 16 and not '[' or '{' — e.g. undecompressed gzip (0x1f), protobuf field tags > 16, protobuf base64 text, XML, or an empty/garbage buffer offset.

Common situations: Forgetting to decompress gzip/deflate from reporters before decoding; feeding base64- or text-encoded spans; reusing a ByteBuffer whose position was advanced by prior reads so detection sees the wrong byte; version mismatches between reporter encoder and collector decoder.

Related errors


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