karatelabs/karate · error · OAuth2Exception

Token exchange failed: " + e.getMessage()

Error message

Token exchange failed: " + e.getMessage()

What it means

Wrapper thrown by exchangeCodeForToken's catch (OAuth2Exception e) block: any OAuth2Exception raised while exchanging the code (invalid response, provider error, etc.) is re-thrown as a new OAuth2Exception with the 'Token exchange failed:' prefix, chained to the original cause.

Solutions

  1. Inspect the cause chain (e.getCause()) — the real reason is the inner OAuth2Exception message.
  2. Read the inner message: 'Token request failed: <code>' means a provider rejection; 'invalid response' means non-JSON body.
  3. Fix the underlying condition per the inner error (credentials, redirect_uri, code reuse, endpoint health).

Example fix

// before: only printing top-level message
logger.error("auth failed: {}", e.getMessage());
// after: log the cause chain
Throwable cause = e.getCause();
logger.error("auth failed: {} (cause: {})", e.getMessage(), cause == null ? "none" : cause.getMessage());
Defensive patterns

Strategy: try-catch

Try / catch

try {
    handler.token();
} catch (OAuth2Exception e) {
    Throwable root = e;
    while (root.getCause() != null) { root = root.getCause(); }
    logger.error("Token exchange failed, root cause: {}", root.getMessage());
}

Prevention

When it happens

Trigger: Any OAuth2Exception thrown inside exchangeCodeForToken — invalid/unexpected token response, provider error field, or lower-level OAuth2 failures — gets re-wrapped with this message.

Common situations: The same situations as errors 280-282, observed at the outer boundary; logs will show 'Token exchange failed: Token request failed: invalid_grant' style chains.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/abfda02a24b76178. Report an issue: GitHub.

Appendix: source

Thrown at karate-core/src/main/java/io/karatelabs/http/AuthorizationCodeAuthHandler.java:217

            } 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);
        }
    }

    /**
     * Start callback server on configured or default ports
     */
    private String startCallbackServer(LocalCallbackServer server) {
        int[] ports = getConfiguredPorts();

        Exception lastException = null;
        for (int port : ports) {
            try {
                String redirectUri = server.start(port);
                logger.debug("Callback server started on port {}", port);
                return redirectUri;

View on GitHub (pinned to a22eb90246)