openzipkin/zipkin · error · UnsupportedOperationException

v2 formats should only be used with list messages

Error message

v2 formats should only be used with list messages

What it means

SpanBytesDecoderDetector.decoderForMessage detects the format of a single-span message; if detection identifies JSON_V2 or PROTO3 (v2 formats), it throws UnsupportedOperationException('v2 formats should only be used with list messages'). Zipkin v2 wire formats (JSON_V2, PROTO3) are only defined as list payloads — e.g. a v2 JSON object '{"id":...}' reaches this path when it starts with '{' and contains endpoint/tags markers.

Source

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

  /**
   * Technically, it is possible to have a v2 span with no endpoints. This should catch the case
   * where someone reported a tag without reporting the "localEndpoint".
   *
   * <p>Note: we don't check for annotations as that exists in both v1 and v2 formats.
   */
  static final byte[] TAGS_FIELD = {'"', 't', 'a', 'g', 's', '"'};

  /**
   * Throws {@link IllegalArgumentException} if the input isn't a v1 json or thrift single-span
   * message
   */
  public static BytesDecoder<Span> decoderForMessage(byte[] span) {
    BytesDecoder<Span> decoder = detectDecoder(ByteBuffer.wrap(span));
    if (span[0] == 12 /* List[ThriftSpan] */ || span[0] == '[') {
      throw new IllegalArgumentException("Expected json or thrift object, not list encoding");
    }
    if (decoder == SpanBytesDecoder.JSON_V2 || decoder == SpanBytesDecoder.PROTO3) {
      throw new UnsupportedOperationException("v2 formats should only be used with list messages");
    }
    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 != '[') {

View on GitHub (pinned to 878ce2a1fa)

Solutions

  1. Switch to decoderForListMessage for v2 payloads and wrap single objects in a one-element array where needed.
  2. Send/receive v2 spans as lists: JSON '[{...}]' via SpanBytesEncoder.JSON_V2.encodeList, or PROTO3 via its list encoding.
  3. If you must support v1 single messages, branch by detected decoder before choosing the message vs list API.

Example fix

// before
BytesDecoder<Span> d = SpanBytesDecoderDetector.decoderForMessage(bytes);

// after
// v2 payloads must travel as lists
byte[] listBytes = SpanBytesEncoder.JSON_V2.encodeList(Collections.singletonList(span));
List<Span> back = SpanBytesDecoder.JSON_V2.decodeList(listBytes);
Defensive patterns

Strategy: type-guard

Validate before calling

// v2 payloads must be list-encoded; wrap single v2 objects before decode
byte[] listBytes = SpanBytesEncoder.JSON_V2.encodeList(spans);

Type guard

boolean isV2Payload(ByteBuffer buf) {
  return SpanBytesDecoderDetector.detectDecoder(buf.duplicate())
      .equals(SpanBytesDecoder.JSON_V2); // or PROTO3 via decoderForMessage guard
}

Try / catch

try {
  decoder = SpanBytesDecoderDetector.decoderForMessage(bytes);
} catch (UnsupportedOperationException e) { // v2 single object
  decoder = SpanBytesDecoder.JSON_V2; spanList = decoder.decodeList(wrapInArray(bytes));
}

Prevention

When it happens

Trigger: Feeding a single JSON_V2 span object or a bare PROTO3-encoded Span to decoderForMessage instead of wrapping it in a list and using decoderForListMessage.

Common situations: Custom transports that deliver one span at a time and reused v1-era decode code; receiving v2 JSON from zipkin-reporter udp/http senders that were assumed to be v1 single objects.

Related errors


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