{"record":{"id":"b611bc1f744751f9","repo":"openzipkin/zipkin","slug":"expected-json-or-thrift-object-not-list-encoding","errorCode":null,"errorMessage":"Expected json or thrift object, not list encoding","messagePattern":"Expected json or thrift object, not list encoding","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"zipkin/src/main/java/zipkin2/SpanBytesDecoderDetector.java","lineNumber":56,"sourceCode":"   */\n  static final byte[] ENDPOINT_FIELD_SUFFIX = {'E', 'n', 'd', 'p', 'o', 'i', 'n', 't', '\"'};\n\n  /**\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());","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/openzipkin/zipkin/blob/878ce2a1fad54ca941d17fdcf2e1d924b148eb1f/zipkin/src/main/java/zipkin2/SpanBytesDecoderDetector.java#L38-L74","documentation":"SpanBytesDecoderDetector.decoderForMessage(byte[]) expects a single-span v1 JSON object or Thrift struct and throws IllegalArgumentException('Expected json or thrift object, not list encoding') when the payload is a list: first byte '[' (JSON array) or 12 (Thrift list-of-span header). Single-message decoding is only for legacy v1 ingestion; lists must go through decoderForListMessage.","triggerScenarios":"Calling decoderForMessage on data produced by zipkin reporters, which send JSON arrays like [{...}] or Thrift List[Span] batches.","commonSituations":"Writing a custom collector/transport that receives spans from zipkin-reporter (Kafka, SQS, HTTP /spans) but reuses single-span decode logic; migrating legacy v1 single-object ingestion to also accept modern reporter payloads.","solutions":["Use SpanBytesDecoderDetector.decoderForListMessage(spans) and decode all spans in the list.","Branch on the first byte: '[' or 12 -> list path; otherwise decoderForMessage.","If you must accept both shapes, JSON_V1 and THRIFT decoders have sizeInBytes/list handling — detect first, then decode one-or-many accordingly."],"exampleFix":"// before\nBytesDecoder<Span> d = SpanBytesDecoderDetector.decoderForMessage(payload);\nSpan span = d.decodeOne(payload);\n\n// after\nBytesDecoder<Span> d = SpanBytesDecoderDetector.decoderForListMessage(payload);\nList<Span> spans = d.decodeList(payload);","handlingStrategy":"type-guard","validationCode":"boolean isListEncoded(byte[] payload) {\n  byte f = payload[0];\n  return f == '[' || f == 12 || f == 11;\n}","typeGuard":"enum SpanPayloadShape { SINGLE, LIST, UNKNOWN }\n\nSpanPayloadShape shapeOf(byte[] p) {\n  if (p.length == 0) return SpanPayloadShape.UNKNOWN;\n  byte f = p[0];\n  if (f == '[' || f == 12 || f == 11) return SpanPayloadShape.LIST;\n  if (f == '{') return SpanPayloadShape.SINGLE;\n  return SpanPayloadShape.UNKNOWN;\n}","tryCatchPattern":null,"preventionTips":["Send and decode reporter payloads as lists (decoderForListMessage) by default.","Keep the single-message API only for legacy v1 paths."],"tags":["zipkin","codec","decode","json","thrift"],"backgroundTag":null,"analyzedSha":"878ce2a1fad54ca941d17fdcf2e1d924b148eb1f","analyzedAt":"2026-08-14T15:17:09.895Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}