openzipkin/zipkin · error · IllegalArgumentException

%s reading %s from json

Error message

%s reading %s from json

What it means

Zipkin's JSON codec wraps every lower-level parse failure (from JsonReader) into an IllegalArgumentException whose message is '<cause> reading <type> from json'. The <type> names the Zipkin model object being decoded (e.g. 'List<Span>', 'Span', 'Endpoint'). Causes mentioning 'Expected BEGIN_OBJECT', 'Expected BEGIN_ARRAY', or 'malformed' are normalized to 'Malformed', so the message you see is usually 'Malformed reading List<Span> from json'. The original exception is preserved as the cause for full diagnostics.

Source

Thrown at zipkin/src/main/java/zipkin2/internal/JsonCodec.java:221

  public static <T> void writeList(WriteBuffer.Writer<T> writer, List<T> value, WriteBuffer b) {
    b.writeByte('[');
    for (int i = 0, length = value.size(); i < length; ) {
      writer.write(value.get(i++), b);
      if (i < length) b.writeByte(',');
    }
    b.writeByte(']');
  }

  static IllegalArgumentException exceptionReading(String type, Exception e) {
    String cause = e.getMessage() == null ? "Error" : e.getMessage();
    if (cause.contains("Expected BEGIN_OBJECT")
      || cause.contains("Expected BEGIN_ARRAY")
      || cause.contains("malformed")) {
      cause = "Malformed";
    }
    String message = format("%s reading %s from json", cause, type);
    throw new IllegalArgumentException(message, e);
  }
}

View on GitHub (pinned to 878ce2a1fa)

Solutions

  1. Inspect the original exception via getCause() (or the logged stack) to see the exact JSON token and offset that failed.
  2. Verify the payload is actually JSON and matches the expected version: try decoding with SpanBytesDecoder.JSON_V1 vs JSON_V2, or PROTO3.
  3. Validate the bytes externally (jq, json.tool) before re-sending; fix truncation at the producer/transport (Content-Length, max payload size).
  4. If writing a collector, catch IllegalArgumentException per message and drop/quarantine the malformed span batch instead of killing the consumer.

Example fix

// before
List<Span> spans = SpanBytesDecoder.JSON_V2.decodeList(bytes);

// after
List<Span> spans;
try {
  spans = SpanBytesDecoder.JSON_V2.decodeList(bytes);
} catch (IllegalArgumentException e) {
  throw new IllegalArgumentException("Bad span payload: " + new String(bytes, UTF_8), e);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// no cheap pre-validation for arbitrary JSON; attempt decode and catch
byte[] bytes = ...;

Try / catch

try { spans = SpanBytesDecoder.JSON_V2.decodeList(bytes); } catch (IllegalArgumentException e) { log.warn("bad span json: {}", e.getMessage()); /* drop/quarantine */ }

Prevention

When it happens

Trigger: Calling SpanBytesDecoder.JSON_V2.decode(byte[]) (or decodeList) with bytes that are not valid JSON, are valid JSON of the wrong shape (e.g. an object where an array of spans is expected), contain malformed UTF-8, or are truncated mid-document. Also triggered by storage/transport layers (zipkin-server, kafka, rabbitmq collectors) decoding an incoming JSON body.

Common situations: A producer sends proto3 or thrift bytes to an endpoint configured for JSON; a truncated HTTP body is forwarded; a proxy re-encodes the payload wrongly; a version mismatch where V1 JSON is sent to a V2 JSON decoder.

Related errors


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