karatelabs/karate · error · IllegalStateException

unexpected FullHttpResponse

Error message

unexpected FullHttpResponse (status=${response.status()}, content=${response.content()})

What it means

WsClientHandler.channelRead0 throws IllegalStateException when a FullHttpResponse arrives after (or outside) the expected handshake processing. In a normal WebSocket lifecycle the handshake response is consumed once; any subsequent full HTTP response on the channel means the pipeline received something it cannot interpret as a WebSocket frame, so the library fails loudly and fails the handshake future.

Solutions

  1. Check the handshakeFuture failure/cause and inspect the status/content in the message to see what the server returned
  2. Verify the WebSocket endpoint URL, path and auth headers (Authorization, cookies) are correct
  3. Ensure any proxy in front of the server supports and forwards the HTTP Upgrade for WebSocket
  4. Capture a wire-level trace (or server access log) of the handshake request/response to confirm a 101 Switching Protocols

Example fix

// before
client.connect("wss://host/api/ws"); // /api/ws returns 404 JSON page
// after
client.connect("wss://host/ws"); // correct endpoint returns 101
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: confirm the endpoint performs a WS upgrade
HttpRequest req = HttpRequest.newBuilder(URI.create(wsUrl.replaceFirst("^ws", "http"))).GET().build();
// expect non-error response and Upgrade headers; a 404/401 page predicts this IllegalStateException

Try / catch

try { client.connect(url).get(10, TimeUnit.SECONDS); } catch (ExecutionException e) { if (e.getCause() instanceof IllegalStateException && e.getCause().getMessage().startsWith("unexpected FullHttpResponse")) { log.error("server rejected upgrade: {}", e.getCause().getMessage()); } }

Prevention

When it happens

Trigger: Server responds with an HTTP error page (e.g. 401/403/404/500) instead of completing the 101 upgrade; duplicate/malformed handshake handling; an HTTP response arriving on an already-established WebSocket channel.

Common situations: Auth gateway returning 302/401 HTML for wss:// requests; reverse proxy intercepting the upgrade; wrong path on the server; server version returning an error after partial handshake.

Related errors


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

Appendix: source

Thrown at karate-core/src/main/java/io/karatelabs/http/WsClientHandler.java:88

        client.handleDisconnect();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (!handshaker.isHandshakeComplete()) {
            try {
                handshaker.finishHandshake(ctx.channel(), (FullHttpResponse) msg);
                logger.debug("websocket handshake complete: {}", client.getUri());
                handshakeFuture.setSuccess();
            } catch (WebSocketHandshakeException e) {
                logger.error("websocket handshake failed: {}", e.getMessage());
                handshakeFuture.setFailure(e);
            }
            return;
        }

        if (msg instanceof FullHttpResponse response) {
            throw new IllegalStateException(
                    "unexpected FullHttpResponse (status=" + response.status() +
                            ", content=" + response.content().toString(StandardCharsets.UTF_8) + ")");
        }

        WebSocketFrame frame = (WebSocketFrame) msg;

        if (frame instanceof TextWebSocketFrame textFrame) {
            onTextFrame(ctx, textFrame);
        } else if (frame instanceof BinaryWebSocketFrame binaryFrame) {
            onBinaryFrame(ctx, binaryFrame);
        } else if (frame instanceof PingWebSocketFrame pingFrame) {
            onPingFrame(ctx, pingFrame);
        } else if (frame instanceof PongWebSocketFrame pongFrame) {
            onPongFrame(ctx, pongFrame);
        } else if (frame instanceof CloseWebSocketFrame closeFrame) {
            onCloseFrame(ctx, closeFrame);
        }
    }

View on GitHub (pinned to a22eb90246)