openzipkin/zipkin · error · IllegalArgumentException

Expected json, proto3 or thrift list encoding

Error message

Expected json, proto3 or thrift list encoding

What it means

SpanBytesDecoderDetector.decoderForListMessage(ByteBuffer) throws IllegalArgumentException('Expected json, proto3 or thrift list encoding') when the first byte does not identify a list container: not 12 (Thrift List[ThriftSpan]), not 11 (legacy thrift empty-list, zipkin-reporter-java#133), not '[', and not a protobuf3 message. It expects payloads in one of the reporter batch encodings.

Source

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

    }
    return decoder;
  }

  /**
   * Throws {@link IllegalArgumentException} if the input isn't a json, proto3 or thrift list
   * message.
   */
  public static BytesDecoder<Span> decoderForListMessage(byte[] spans) {
    return decoderForListMessage(ByteBuffer.wrap(spans));
  }

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

View on GitHub (pinned to 878ce2a1fa)

Solutions

  1. Ensure the payload is decompressed before detection (gunzip when Content-Encoding is gzip).
  2. Produce the payload with a reporter's list encoder (JSON_V2.encodeList / PROTO3 / Thrift) rather than concatenating raw objects.
  3. For mixed-shape endpoints, branch on the first byte: '{' -> decoderForMessage path, '[' / 12 / 11 -> list path.

Example fix

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

// after
byte[] raw = maybeGunzip(body); // handle Content-Encoding first
BytesDecoder<Span> d = raw[0] == '{'
    ? SpanBytesDecoderDetector.decoderForMessage(raw)
    : SpanBytesDecoderDetector.decoderForListMessage(raw);
Defensive patterns

Strategy: try-catch

Validate before calling

byte first = spans.get(spans.position());
if (first != '[' && first != '{' && first > 16 && first != 11 && first != 12) {
  // reject early: unsupported or undecoded (compressed?) payload
}

Try / catch

try {
  decoder = SpanBytesDecoderDetector.decoderForListMessage(buf);
} catch (IllegalArgumentException e) {
  // fall back to single-message decoding for '{' payloads, otherwise drop + metric
  decoder = SpanBytesDecoderDetector.decoderForMessage(bytes);
}

Prevention

When it happens

Trigger: Passing a single JSON object ('{' first byte), a raw PROTO3 Span struct that fails the protobuf3 heuristic, or arbitrary/corrupt bytes to decoderForListMessage.

Common situations: Collector endpoints receiving mixed payloads (some clients send single objects); truncated or double-compressed payloads (gzip not yet decompressed) whose first byte is garbage; encoding mismatches after upgrading a reporter.

Related errors


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