karatelabs/karate · critical · OAuth2Exception

Could not start callback server on any configured port: " +…

Error message

Could not start callback server on any configured port: " + java.util.Arrays.toString(ports) + ". " + "Please ensure at least one port is available, or configure different ports using 'callbackPort' or 'callbackPorts'. " + "These ports must be registered as redirect URIs with your OAuth provider."

What it means

Thrown by startCallbackServer (reached via redirectUri) when it fails to bind the local HTTP callback server on every configured port. The local loopback server must be listening before the authorization redirect can be received; without it the OAuth code flow cannot complete.

Solutions

  1. Find and stop the process holding the port (lsof -i :<port> / netstat -ano), or kill the stale previous run.
  2. Configure a different free port via callbackPort, or several via callbackPorts, and register all of them as redirect URIs with your OAuth provider.
  3. Use a privileged high port (>1024) to avoid permission issues, and check container port-binding restrictions if running in Docker.

Example fix

// before
.callbackPort(8080) // occupied by a dev server
// after
.callbackPorts("9090,9091,9092") // free ports, registered with provider
// and register http://localhost:9090/* etc. as allowed redirect URIs
Defensive patterns

Strategy: fallback

Validate before calling

// Pre-check that at least one callback port is free before starting the flow
int[] ports = {9090, 9091, 9092};
boolean anyFree = false;
for (int p : ports) {
    try (java.net.ServerSocket ss = new java.net.ServerSocket(p)) { anyFree = true; break; }
    catch (java.io.IOException ignored) {}
}
if (!anyFree) throw new IllegalStateException("No callback port free: " + java.util.Arrays.toString(ports));

Try / catch

try {
    handler.token();
} catch (OAuth2Exception e) {
    if (e.getMessage().startsWith("Could not start callback server")) {
        // free the ports (kill stale process) or reconfigure callbackPorts and restart
    }
}

Prevention

When it happens

Trigger: All ports in callbackPort/callbackPorts (or defaults) are already in use by other processes, or the OS refuses to bind (permissions, disabled loopback), so every ServerSocket bind attempt throws and the loop exhausts the port list.

Common situations: Another instance of the test/agent still running and holding the port; a dev server occupying the configured callbackPort; Docker/container environments where loopback binding is restricted; stale processes after a crashed previous run.

Related errors


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

Appendix: source

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

     * 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;
            } catch (Exception e) {
                logger.warn("Port {} in use, trying next port", port);
                lastException = e;
            }
        }

        // If all configured ports fail, provide helpful error message
        throw new OAuth2Exception(
            "Could not start callback server on any configured port: " + java.util.Arrays.toString(ports) + ". " +
            "Please ensure at least one port is available, or configure different ports using 'callbackPort' or 'callbackPorts'. " +
            "These ports must be registered as redirect URIs with your OAuth provider.",
            lastException
        );
    }

    /**
     * Get configured callback ports or use defaults
     */
    private int[] getConfiguredPorts() {
        // Check for single port configuration
        Object callbackPort = config.get("callbackPort");
        if (callbackPort != null) {
            return new int[] { parsePort(callbackPort) };
        }

        // Check for multiple ports configuration

View on GitHub (pinned to a22eb90246)