apache/druid · error · IllegalArgumentException
Next token wasn't a START_ARRAY, was
Error message
Next token wasn't a START_ARRAY, was[%s] from url[%s] with value[%s]
What it means
JsonParserIterator expects the HTTP response body to start with a JSON START_ARRAY token (the standard Druid results array). If the first token is anything else, init() throws this IAE showing the actual token type, the URL, and a truncated (192-char) snapshot of the payload. This usually means the server returned an error or non-standard JSON object instead of the expected results array.
Solutions
- Read the value[%s] in the message: it usually contains the real server error; fix that underlying issue first
- Confirm broker and data node versions are compatible (rolling-upgrade skew can change response shapes)
- Check for proxies/load balancers intercepting the URL and returning non-JSON bodies
- Retry the query; if persistent, capture the raw response from the data node endpoint for diagnosis
Defensive patterns
Strategy: try-catch
Validate before calling
// verify cluster version skew before issuing queries // all data nodes should report the same/major-compatible version as the broker
Try / catch
try {
stream = iterator; // triggers init()
} catch (IAE e) {
if (e.getMessage().startsWith("Next token wasn't a START_ARRAY")) {
// log full message: contains upstream error payload in value[...]
// surface underlying server error, do not blindly retry
}
} Prevention
- Inspect the value[...] field in the message — it usually carries the real server error
- Keep broker and data node versions compatible during rolling upgrades
- Bypass or fix proxies that return HTML error pages on the query path
- Retry only after confirming the upstream error is transient
When it happens
Trigger: The response stream from url[%s] parses to a token that is not START_ARRAY — e.g. a JSON error object {"error":...}, a bare string, or HTML from a misbehaving proxy — encountered when hasNext()/next() call init().
Common situations: Downstream server returning a serialized QueryException or plain error JSON, an intermediate proxy/load balancer replacing the body with an HTML error page, or API/protocol mismatch between broker and older/newer data node versions.
Related errors
- Cannot coerce value [ ] of type [ ] for column [ ] to
- Cannot handle subquery
- Cannot have a null result!
- columnNames and columnTypes must be the same length
- Could not convert task
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/59f232c36b6cdad3.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/main/java/org/apache/druid/client/JsonParserIterator.java:202
// this exception thrown for other reasons, it would be great to document what other reasons this can happen.
throw ResourceLimitExceededException.withMessage(
"Possibly max scatter-gather bytes limit reached while reading from url[%s].",
url
);
}
final JsonToken nextToken = jp.nextToken();
if (nextToken == JsonToken.START_ARRAY) {
jp.nextToken();
objectCodec = jp.getCodec();
} else if (nextToken == JsonToken.START_OBJECT) {
throw convertException(jp.getCodec().readValue(jp, QueryException.class));
} else {
String errMsg = jp.getValueAsString();
if (errMsg != null) {
errMsg = errMsg.substring(0, Math.min(errMsg.length(), 192));
}
throw convertException(
new IAE(
"Next token wasn't a START_ARRAY, was[%s] from url[%s] with value[%s]",
jp.getCurrentToken(),
url,
errMsg
)
);
}
}
catch (ExecutionException | CancellationException e) {
throw convertException(e.getCause() == null ? e : e.getCause());
}
catch (IOException | InterruptedException e) {
throw convertException(e);
}
catch (TimeoutException e) {
throw new QueryTimeoutException(StringUtils.nonStrictFormat("Query [%s] timed out!", queryId), host);
}View on GitHub (pinned to 9b90983fd2)