openzipkin/zipkin · error · IllegalArgumentException
Expected start object, was %s
Error message
Expected start object, was %s
What it means
JsonReaders.checkStartObject(parser, shouldThrow) verifies the parser is positioned on (or can advance to) a START_OBJECT token. With shouldThrow=true, any other token — or a non-JSON body, which makes the parser throw — is surfaced as IllegalArgumentException('Expected start object, was <token>'). It is used when entering objects of search responses, so it fires when the response is not the JSON object shape the client expects.
Source
Thrown at zipkin-storage/elasticsearch/src/main/java/zipkin2/elasticsearch/internal/JsonReaders.java:105
break;
case START_OBJECT:
visitObject(parser, name, result);
break;
default:
// Skip current value.
}
}
static boolean checkStartObject(JsonParser parser, boolean shouldThrow) throws IOException {
try {
JsonToken currentToken = parser.currentToken();
// The parser may not be at a token, yet. If that's the case advance.
if (currentToken == null) currentToken = parser.nextToken();
// If we are still not at the expected token, we could be an another or an empty body.
if (currentToken == JsonToken.START_OBJECT) return true;
if (shouldThrow) {
throw new IllegalArgumentException("Expected start object, was " + currentToken);
}
return false;
} catch (Throwable e) { // likely not json
if (shouldThrow) throw e;
return false;
}
}
JsonReaders() {
}
}
View on GitHub (pinned to 878ce2a1fa)
Solutions
- Capture the raw HTTP response the storage receives (curl the same URL Zipkin uses) and compare it with a known-good Elasticsearch/OpenSearch response.
- Remove the intermediary rewriting responses, or point ES_HOSTS directly at real ES nodes.
- Match the zipkin-server version to your cluster version so expected response shapes agree.
- Check ES credentials/permissions if the body is an auth error delivered with 200.
Example fix
# before: ES_HOSTS=http://es:9300 (transport port, returns non-JSON) # -> IllegalArgumentException: Expected start object, was ... # after: ES_HOSTS=http://es:9200 (HTTP port returning JSON objects)
Defensive patterns
Strategy: try-catch
Validate before calling
// confirm endpoints return JSON objects before wiring storage
var resp = http.get(esHost + "/_cluster/health");
if (!resp.body().trim().startsWith("{")) {
throw new IllegalStateException("Expected JSON object from ES, got: " + resp.body().substring(0, 80));
} Try / catch
try {
V result = call.execute();
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Expected start object")) {
// response is not the JSON object shape expected: check proxy/auth/ES version skew
}
throw e;
} Prevention
- Point ES_HOSTS at real ES HTTP ports (9200) verified with curl.
- Keep proxies from rewriting ES responses; check for HTML error pages on auth failures.
- Match zipkin-server and cluster versions to avoid response-shape drift.
When it happens
Trigger: An Elasticsearch search/cluster call returns a JSON array, a scalar, an error envelope at the wrong depth, or a non-JSON body (HTML error page, empty-but-status-200) at a point where Zipkin's internal readers call checkStartObject with shouldThrow=true.
Common situations: A proxy or captive portal returning HTML; ES returning an error body with HTTP 200; version-skew where response shapes changed; hitting a non-ES port.
Related errors
- no {name} property in {fileName}
- empty {name} property in {fileName}
- No valid endpoints found in ES hosts: {hosts}
- dateSeparator must be empty or a single character
- No content reading Elasticsearch/OpenSearch version
AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14).
Data as JSON: /api/errors/41f970565246e5e3.
Report an issue: GitHub.