grpc/grpc-java · error · IOException
Received an unexpected response from a host at the S2A's add
Error message
Received an unexpected response from a host at the S2A's address.
What it means
When S2AStub.send() receives a failed response from the S2A stream without an IOException cause, it throws a plain IOException saying it received an unexpected response from a host at the S2A's address. This variant ( no 'S2A might be unavailable' suffix ) indicates the response failed but the failure type is not I/O related.
Source
Thrown at s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AStub.java:126
if (doneWriting && doneReading) {
logger.log(Level.INFO, "Stream to the S2A is closed.");
throw new ConnectionClosedException("Stream to the S2A is closed.");
}
createWriterIfNull();
if (!responses.isEmpty()) {
IOException exception = null;
try {
responses.take().getResultOrThrow();
} catch (IOException e) {
exception = e;
}
responses.clear();
if (exception != null) {
throw new IOException(
"Received an unexpected response from a host at the S2A's address. The S2A might be"
+ " unavailable.", exception);
} else {
throw new IOException("Received an unexpected response from a host at the S2A's address.");
}
}
try {
writer.onNext(req);
} catch (RuntimeException e) {
writer.onError(e);
responses.add(Result.createWithThrowable(e));
}
try {
return responses.take().getResultOrThrow();
} catch (ConnectionClosedException e) {
// A ConnectionClosedException is thrown by getResultOrThrow when reader calls its
// onCompleted method. The close method is called to also close the writer, and then the
// ConnectionClosedException is re-thrown in order to indicate to the caller that send
// should not be called again.
close();
throw e;
}View on GitHub (pinned to 64daddc1f3)
Solutions
- Confirm the configured S2A address serves the actual S2A handshaker service.
- Check S2A process logs for the error it returned on the stream.
- Recreate the stub/channel and retry after the stream is reset.
Example fix
// before
// address points to unrelated service
channel = ManagedChannelBuilder.forTarget("10.0.0.5:9000").usePlaintext().build();
// after
channel = ManagedChannelBuilder.forTarget(s2aProcess.socketPath()).build(); Defensive patterns
Strategy: validation
Validate before calling
// ensure the gRPC channel speaks to the real S2A handshaker service
if (!channelHealthCheckForS2aService(channel)) {
throw new IllegalStateException("Endpoint does not serve the S2A handshaker service");
} Try / catch
try {
resp = stub.send(req);
} catch (IOException e) {
if (e.getCause() == null) {
// unexpected non-IO failure: suspect wrong endpoint or error status
logger.warning("Non-IO failure from S2A address: " + e.getMessage());
}
throw e;
} Prevention
- Pin the S2A address to the S2A process's socket/port, not a shared endpoint.
- Check S2A logs when it returns error statuses on the stream.
- Recreate the stub after any stream error.
When it happens
Trigger: Calling S2AStub.send() when responses.take().getResultOrThrow() throws a non-IOException failure, or the taken response is an error result without an IOException — e.g. a status error from the wrong/unexpected gRPC server at the S2A address.
Common situations: Something other than the S2A ( e.g. an unrelated gRPC server ) is listening at the configured address; S2A returned an error status on the stream.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- No valid response received from S2A.
- Stream to the S2A is closed.
- Received an unexpected response from a host at the S2A's add
- Failed to send request to S2A.
- Failed to get client TLS configuration from S2A.
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/cba66e33706bd0f4.
Report an issue: GitHub.