{"record":{"id":"bde48bb09c29ec68","repo":"grpc/grpc-java","slug":"received-an-unexpected-response","errorCode":null,"errorMessage":"Received an unexpected response.","messagePattern":"Received an unexpected response\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java","lineNumber":65,"sourceCode":"  }\n\n  @VisibleForTesting\n  AltsHandshakerStub(StreamObserver<HandshakerReq> writer) {\n    this.writer = writer;\n    serviceStub = null;\n  }\n\n  @VisibleForTesting\n  StreamObserver<HandshakerResp> getReaderForTest() {\n    return reader;\n  }\n\n  /** Send a handshaker request and return the handshaker response. */\n  public HandshakerResp send(HandshakerReq req) throws InterruptedException, IOException {\n    createWriterIfNull();\n    maybeThrowIoException();\n    if (!responseQueue.isEmpty()) {\n      throw new IOException(\"Received an unexpected response.\");\n    }\n\n    writer.onNext(req);\n    Optional<HandshakerResp> result = responseQueue.take();\n    if (result.isPresent()) {\n      return result.get();\n    }\n\n    if (exceptionMessage.get() != null) {\n      throw new IOException(exceptionMessage.get().info, exceptionMessage.get().throwable);\n    } else {\n      throw new IOException(\"No handshaker response received\");\n    }\n  }\n\n  /** Create a new writer if the writer is null. */\n  private void createWriterIfNull() {\n    if (writer == null) {","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java#L47-L83","documentation":"AltsHandshakerStub.send() is strictly request/response: it rejects a send when the responseQueue already holds an unconsumed response. Finding a queued response means the previous response was never taken (protocol desynchronization between client requests and handshaker responses), so it throws IOException(\"Received an unexpected response.\").","triggerScenarios":"Calling send() while a previous HandshakerResp is still queued — e.g. sending a new HandshakerReq before consuming the prior response, or the handshaker service pushing an extra unsolicited response.","commonSituations":"Handshake driver bugs that call next()/send() out of order, duplicate responses from a misbehaving handshaker service, or concurrent use of the stub from multiple threads without synchronization.","solutions":["Ensure each send() result is consumed before the next request is issued (one request in flight at a time)","Do not share the AltsHandshakerStub across threads — serialize handshake steps","Close and rebuild the handshaker stub/connection; the queue state is unrecoverable once desynchronized","Update grpc-alts: older versions had handshake-stream race fixes"],"exampleFix":"// before\nstub.send(req1);\nstub.send(req2); // response from req1 still queued\n// after\nHandshakerResp resp = stub.send(req1);\nprocess(resp);\nHandshakerResp resp2 = stub.send(req2); // send only after consuming previous response","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  HandshakerResp resp = stub.send(req);\n} catch (IOException e) {\n  stub.close();\n  stub = newStub(handshakerAddr); // queue state is unrecoverable\n}","preventionTips":["Keep at most one in-flight handshaker request; consume each response before the next send","Never share the stub across threads","Rebuild the stub after any IOException"],"tags":["grpc","alts","handshake","protocol","state"],"backgroundTag":"invalid-state-transition","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}