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
- Inspect the original exception via getCause() (or the logged stack) to see the exact JSON token and offset that failed.
- Verify the payload is actually JSON and matches the expected version: try decoding with SpanBytesDecoder.JSON_V1 vs JSON_V2, or PROTO3.
- Validate the bytes externally (jq, json.tool) before re-sending; fix truncation at the producer/transport (Content-Length, max payload size).
- 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
- Pin one encoding (JSON_V2) across producers and collectors; set Content-Type explicitly.
- Round-trip test encode/decode in CI for every pipeline component.
- Never hand-build span JSON; use SpanBytesEncoder.
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.