oracle/graal · error · IOException

handshake failed - unrecognized message from the debugger

Error message

handshake failed - unrecognized message from the debugger

What it means

Thrown by HandshakeController.handshake when the full 14 bytes arrived but do not equal the ASCII string 'JDWP-Handshake'. The JDWP wire protocol requires both peers to begin with exactly that greeting; any other bytes mean the peer is not speaking JDWP, and the agent refuses to continue.

Source

Thrown at espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/HandshakeController.java:127

        byte[] b = new byte[hello.length];
        int received = 0;
        while (received < hello.length) {
            int n;
            try {
                n = s.getInputStream().read(b, received, hello.length - received);
            } catch (SocketTimeoutException x) {
                throw new IOException("handshake timeout");
            }
            if (n < 0) {
                s.close();
                throw new IOException("handshake failed - connection prematurely closed");
            }
            received += n;
        }
        for (int i = 0; i < hello.length; i++) {
            if (b[i] != hello[i]) {
                throw new IOException("handshake failed - unrecognized message from the debugger");
            }
        }

        // handshake received, so return the gesture to establish the jdwp transport
        s.getOutputStream().write(hello);
        return true;
    }
}

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Point the JDWP client at the actual agent port (jdwp=... at the Espresso launcher, e.g. --jdb or agentlib-style flag) and the HTTP client at the app port
  2. Do not put a generic reverse proxy in front of the JDWP listener
  3. Log the offending first bytes to identify which client is misconnecting
Defensive patterns

Strategy: try-catch

Try / catch

catch (IOException e) when message contains 'unrecognized message': log the first bytes received; do not retry the same peer automatically (it is not a JDWP client).

Prevention

When it happens

Trigger: An HTTP client (curl), an SMTP/other protocol client, a binary protocol speaker, or a wrong-version tool connects to the JDWP socket and sends its own protocol banner, which then mismatches the expected greeting byte-for-byte.

Common situations: Debug port confused with the application's HTTP port; a reverse proxy in front of the JVM routing browser requests to the JDWP listener; scripts probing the port with arbitrary payloads.

Understand the failure class

Related errors


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/dcaa5abb21f029d9. Report an issue: GitHub.