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
- Verify only a real JDWP client (jdb -attach, IntelliJ/VS Code Java debugger) connects to the agent port
- Move health checks to a different port so they never touch the JDWP listener
- 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
- Reserve the JDWP port for debugger traffic only
- Expose JDWP on loopback or an authenticated tunnel, not on shared service ports
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- handshake timeout
- handshake failed - connection prematurely closed
- handshake failed - unrecognized message from the debugger
- Invalid JDWP option value: {key} can be only 'y' or 'n'.
- JDWP options must be a comma separated list of key=value pai
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/fc1c618e2fc83f84.
Report an issue: GitHub.