{"record":{"id":"d0cd1ffef7ae76cb","repo":"openzipkin/zipkin","slug":"v2-formats-should-only-be-used-with-list-messages","errorCode":null,"errorMessage":"v2 formats should only be used with list messages","messagePattern":"v2 formats should only be used with list messages","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"zipkin/src/main/java/zipkin2/SpanBytesDecoderDetector.java","lineNumber":59,"sourceCode":"  /**\n   * Technically, it is possible to have a v2 span with no endpoints. This should catch the case\n   * where someone reported a tag without reporting the \"localEndpoint\".\n   *\n   * <p>Note: we don't check for annotations as that exists in both v1 and v2 formats.\n   */\n  static final byte[] TAGS_FIELD = {'\"', 't', 'a', 'g', 's', '\"'};\n\n  /**\n   * Throws {@link IllegalArgumentException} if the input isn't a v1 json or thrift single-span\n   * message\n   */\n  public static BytesDecoder<Span> decoderForMessage(byte[] span) {\n    BytesDecoder<Span> decoder = detectDecoder(ByteBuffer.wrap(span));\n    if (span[0] == 12 /* List[ThriftSpan] */ || span[0] == '[') {\n      throw new IllegalArgumentException(\"Expected json or thrift object, not list encoding\");\n    }\n    if (decoder == SpanBytesDecoder.JSON_V2 || decoder == SpanBytesDecoder.PROTO3) {\n      throw new UnsupportedOperationException(\"v2 formats should only be used with list messages\");\n    }\n    return decoder;\n  }\n\n  /**\n   * Throws {@link IllegalArgumentException} if the input isn't a json, proto3 or thrift list\n   * message.\n   */\n  public static BytesDecoder<Span> decoderForListMessage(byte[] spans) {\n    return decoderForListMessage(ByteBuffer.wrap(spans));\n  }\n\n  public static BytesDecoder<Span> decoderForListMessage(ByteBuffer spans) {\n    BytesDecoder<Span> decoder = detectDecoder(spans);\n    byte first = spans.get(spans.position());\n    if (first != 12 /* List[ThriftSpan] */\n        && first != 11 /* openzipkin/zipkin-reporter-java#133 */\n        && !protobuf3(spans) && first != '[') {","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/openzipkin/zipkin/blob/878ce2a1fad54ca941d17fdcf2e1d924b148eb1f/zipkin/src/main/java/zipkin2/SpanBytesDecoderDetector.java#L41-L77","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Switch to decoderForListMessage for v2 payloads and wrap single objects in a one-element array where needed.","Send/receive v2 spans as lists: JSON '[{...}]' via SpanBytesEncoder.JSON_V2.encodeList, or PROTO3 via its list encoding.","If you must support v1 single messages, branch by detected decoder before choosing the message vs list API."],"exampleFix":"// before\nBytesDecoder<Span> d = SpanBytesDecoderDetector.decoderForMessage(bytes);\n\n// after\n// v2 payloads must travel as lists\nbyte[] listBytes = SpanBytesEncoder.JSON_V2.encodeList(Collections.singletonList(span));\nList<Span> back = SpanBytesDecoder.JSON_V2.decodeList(listBytes);","handlingStrategy":"type-guard","validationCode":"// v2 payloads must be list-encoded; wrap single v2 objects before decode\nbyte[] listBytes = SpanBytesEncoder.JSON_V2.encodeList(spans);","typeGuard":"boolean isV2Payload(ByteBuffer buf) {\n  return SpanBytesDecoderDetector.detectDecoder(buf.duplicate())\n      .equals(SpanBytesDecoder.JSON_V2); // or PROTO3 via decoderForMessage guard\n}","tryCatchPattern":"try {\n  decoder = SpanBytesDecoderDetector.decoderForMessage(bytes);\n} catch (UnsupportedOperationException e) { // v2 single object\n  decoder = SpanBytesDecoder.JSON_V2; spanList = decoder.decodeList(wrapInArray(bytes));\n}","preventionTips":["Standardize on list encoding for all v2 wire traffic.","Pin reporter and collector codec versions together."],"tags":["zipkin","codec","decode","proto3","json-v2"],"backgroundTag":null,"analyzedSha":"878ce2a1fad54ca941d17fdcf2e1d924b148eb1f","analyzedAt":"2026-08-14T15:17:09.895Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}