oracle/graal · error · IOException

Unable to handshake with debugger

Error message

Unable to handshake with debugger

What it means

Thrown by DebuggerConnection when HandshakeController.handshake(socket) returns false after a debugger connection was accepted. The JDWP transport begins with both sides exchanging the 14-byte ASCII string 'JDWP-Handshake'; if the gesture cannot be completed, the connection setup aborts with this IOException. In practice handshake() either throws a specific sub-case (timeout, premature close, unrecognized message) or returns true, so this message marks a handshake failure that produced no finer diagnosis.

Source

Thrown at espresso/src/com.oracle.truffle.espresso.jdwp/src/com/oracle/truffle/espresso/jdwp/impl/DebuggerConnection.java:139

            this.latch = latch;
        }

        @Override
        public void run() {
            // first, complete the connection setup which is potentially blocking
            DebuggerConnection debuggerConnection;
            try {
                Socket connectionSocket;
                if (setupState.socket != null) {
                    connectionSocket = setupState.socket;
                } else { // we know we have a server socket then
                    assert setupState.serverSocket != null;
                    // this blocks until a debugger connects
                    connectionSocket = setupState.serverSocket.accept();
                }
                // OK, ready to do the handshake with debugger
                if (!HandshakeController.handshake(connectionSocket)) {
                    throw new IOException("Unable to handshake with debugger");
                }
                try {
                    if (controller.isClosing()) {
                        return;
                    }
                    // The following block has to be synchronized with resetting, so that
                    // we can abandon further work in case we're told to tear down
                    controller.getResettingLock().lockInterruptibly();
                    // re-check to return immediately if closing
                    if (controller.isClosing()) {
                        return;
                    }
                    SocketConnection socketConnection = new SocketConnection(connectionSocket);
                    debuggerConnection = new DebuggerConnection(socketConnection, controller);
                    controller.setDebuggerConnection(debuggerConnection);
                    controller.getEventListener().setConnection(socketConnection);
                    if (!controller.isSuspend()) {
                        // Fire the vm started event for the suspend=n case.

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Verify only a real JDWP client (jdb -attach, IntelliJ/VS Code Java debugger) connects to the agent port
  2. Move health checks to a different port so they never touch the JDWP listener
  3. If it persists, capture the exact handshake bytes with a proxy to see what the peer sends
Defensive patterns

Strategy: try-catch

Try / catch

catch (IOException e) when message contains 'Unable to handshake': log the peer address, close the socket, and re-enter the accept loop; real debuggers can reconnect.

Prevention

When it happens

Trigger: Connecting to the Espresso JDWP agent's socket with a client that is not a JDWP debugger (curl, telnet, a health-check probe), or a debugger that drops or never completes the initial handshake exchange.

Common situations: Kubernetes/TCP load-balancer health probes hitting the JDWP port instead of the app port; connecting jdb/IDE to the wrong port; a debugger version speaking a nonstandard initial exchange.

Understand the failure class

Related errors


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