karatelabs/karate · error · OAuth2Exception
Token endpoint returned unexpected response: " +…
Error message
Token endpoint returned unexpected response: " + truncate(bodyString)
What it means
Thrown by exchangeCodeForToken when the token endpoint returns valid JSON but the top-level value is not a JSON object. RFC 6749 requires the token response to be a JSON object; arrays, strings, or numbers are rejected so the library cannot extract access_token and related fields.
Solutions
- Confirm the provider's token endpoint returns a JSON object like {"access_token":"...","token_type":"..."}.
- If testing against a mock, fix the mock to return a top-level JSON object.
- Check you are not accidentally calling a different endpoint (e.g. a JWKS or userinfo URL) that returns different JSON shapes.
Example fix
// mock token endpoint before
return Json.array().append("token-value").toString();
// after
return Json.object().set("access_token", "token-value").set("token_type", "bearer").toString(); Defensive patterns
Strategy: validation
Validate before calling
// If you control a mock/stub token endpoint, assert the shape before serving
String body = mockTokenResponse();
Json json = Json.of(body);
if (!json.isObject()) {
throw new IllegalStateException("Token mock must return a JSON object");
} Try / catch
try {
handler.token();
} catch (OAuth2Exception e) {
if (e.getMessage().contains("unexpected response")) {
logger.error("Token endpoint returned non-object JSON: {}", e.getMessage());
}
} Prevention
- Verify provider docs: token responses are always JSON objects.
- Keep mocks/stubs aligned with RFC 6749 response shape.
- Do not reuse JWKS/userinfo URLs as the token endpoint.
When it happens
Trigger: Token endpoint responded with parseable JSON whose root is an array, string, number, or boolean instead of an object (json.isObject() is false).
Common situations: Misconfigured provider or mock/stub returning a JSON array; a custom or poorly-implemented OAuth server returning a bare JSON string; hitting an API that echoes requests as JSON arrays.
Related errors
- Token endpoint returned invalid response: " +…
- Token request failed: " + error + (desc != null ? " - " +…
- client credentials auth request failed: " + e.getMessage()
- missing argument for paramJson()
- invalid json: input is null or blank
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/f42c01b5e7a12814.
Report an issue: GitHub.
Appendix: source
Thrown at karate-core/src/main/java/io/karatelabs/http/AuthorizationCodeAuthHandler.java:203
// Optional client_secret (for confidential clients)
if (config.containsKey("client_secret")) {
builder.formField("client_secret", config.get("client_secret"));
}
builder.header("Accept", "application/json");
try {
HttpResponse response = builder.invoke("post");
String bodyString = response.getBodyString();
Json json;
try {
json = Json.of(bodyString);
} catch (Exception e) {
throw new OAuth2Exception("Token endpoint returned invalid response: " + truncate(bodyString));
}
if (!json.isObject()) {
throw new OAuth2Exception("Token endpoint returned unexpected response: " + truncate(bodyString));
}
Map<String, Object> data = json.asMap();
if (data.containsKey("error")) {
String error = String.valueOf(data.get("error"));
String desc = data.containsKey("error_description") ? String.valueOf(data.get("error_description")) : null;
throw new OAuth2Exception("Token request failed: " + error + (desc != null ? " - " + desc : ""));
}
logger.debug("Token exchange successful");
return OAuth2Token.fromMap(data);
} catch (OAuth2Exception e) {
logger.error("Token exchange failed: {}", e.getMessage());
throw new OAuth2Exception("Token exchange failed: " + e.getMessage(), e);
} catch (Exception e) {
logger.error("Token exchange failed: {}", e.getMessage());
throw new OAuth2Exception("Token exchange failed: " + e.getMessage(), e);
}View on GitHub (pinned to a22eb90246)