grpc/grpc-java · error · IOException

Received an unexpected response.

Error message

Received an unexpected response.

What it means

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.").

Solutions

  1. Ensure each send() result is consumed before the next request is issued (one request in flight at a time)
  2. Do not share the AltsHandshakerStub across threads — serialize handshake steps
  3. Close and rebuild the handshaker stub/connection; the queue state is unrecoverable once desynchronized
  4. Update grpc-alts: older versions had handshake-stream race fixes

Example fix

// before
stub.send(req1);
stub.send(req2); // response from req1 still queued
// after
HandshakerResp resp = stub.send(req1);
process(resp);
HandshakerResp resp2 = stub.send(req2); // send only after consuming previous response
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try {
  HandshakerResp resp = stub.send(req);
} catch (IOException e) {
  stub.close();
  stub = newStub(handshakerAddr); // queue state is unrecoverable
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/bde48bb09c29ec68. Report an issue: GitHub.

Appendix: source

Thrown at alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java:65

  }

  @VisibleForTesting
  AltsHandshakerStub(StreamObserver<HandshakerReq> writer) {
    this.writer = writer;
    serviceStub = null;
  }

  @VisibleForTesting
  StreamObserver<HandshakerResp> getReaderForTest() {
    return reader;
  }

  /** Send a handshaker request and return the handshaker response. */
  public HandshakerResp send(HandshakerReq req) throws InterruptedException, IOException {
    createWriterIfNull();
    maybeThrowIoException();
    if (!responseQueue.isEmpty()) {
      throw new IOException("Received an unexpected response.");
    }

    writer.onNext(req);
    Optional<HandshakerResp> result = responseQueue.take();
    if (result.isPresent()) {
      return result.get();
    }

    if (exceptionMessage.get() != null) {
      throw new IOException(exceptionMessage.get().info, exceptionMessage.get().throwable);
    } else {
      throw new IOException("No handshaker response received");
    }
  }

  /** Create a new writer if the writer is null. */
  private void createWriterIfNull() {
    if (writer == null) {

View on GitHub (pinned to 64daddc1f3)