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. The S2A might be unavailable.
What it means
S2AStub.send() reads responses from the S2A stream via getResultOrThrow(). If that call raises an IOException, send() wraps it in a new IOException stating an unexpected response was received from a host at the S2A's address, suggesting the S2A might be unavailable. The original IOException is preserved as the cause.
Source
Thrown at s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2AStub.java:122
* writer} calls their {@code onError} method.
*/
@SuppressWarnings("CheckReturnValue")
public SessionResp send(SessionReq req) throws IOException, InterruptedException {
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 sendView on GitHub (pinned to 64daddc1f3)
Solutions
- Verify the S2A address/port configuration points at the real S2A process.
- Check that the S2A process is running and healthy; restart it if it crashed.
- Inspect the cause chain for the underlying IOException and recreate the stub after a stream failure.
Example fix
// before
// S2A address from stale config
S2AStub stub = new S2AStub(channelTo("localhost:9000"));
// after
S2AStub stub = new S2AStub(channelTo(config.getS2AAddress())); // correct host/port Defensive patterns
Strategy: retry
Validate before calling
// probe the S2A endpoint before handshakes
if (!s2aHealthCheck(address)) {
throw new IllegalStateException("S2A not reachable/healthy at " + address);
} Try / catch
try {
resp = stub.send(req);
} catch (IOException e) {
if (e.getCause() != null) {
logger.warning("S2A stream failure: " + e.getCause());
}
throw e;
} Prevention
- Validate the S2A address/port configuration at startup.
- Monitor S2A process liveness and restart on failure.
- Log the cause chain to distinguish wrong-endpoint vs process-crash.
When it happens
Trigger: Calling S2AStub.send() and the pending SessionResp future completes exceptionally with an IOException — typically when the endpoint at the S2A's address is not actually the S2A ( wrong host/port ) or the S2A process died mid-stream.
Common situations: Misconfigured S2A address pointing at the wrong service; S2A process crashed between request and response; network interruption during a handshake validation.
Related errors
- 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.
- response from S2A server has ean error %d with error message
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/46b770eb69b76531.
Report an issue: GitHub.