grpc/grpc-java · critical · CertificateException
Failed to send request to S2A.
Error message
Failed to send request to S2A.
What it means
S2ATrustManager.checkPeerTrusted() wraps any IOException from stub.send() into a CertificateException with the message 'Failed to send request to S2A.'. This converts stream-transport failures to the S2A into the X509 TrustManager API's checked exception type so the TLS stack can fail the handshake.
Source
Thrown at s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2ATrustManager.java:154
.addAllCertificateChain(certificateChainToDerChain(chain)));
} else {
validatePeerCertificateChainReq.setServerPeer(
ValidatePeerCertificateChainReq.ServerPeer.newBuilder()
.addAllCertificateChain(certificateChainToDerChain(chain))
.setServerHostname(hostname));
}
SessionReq.Builder reqBuilder =
SessionReq.newBuilder().setValidatePeerCertificateChainReq(validatePeerCertificateChainReq);
if (localIdentity.isPresent()) {
reqBuilder.setLocalIdentity(localIdentity.get().getIdentity());
}
SessionResp resp;
try {
resp = stub.send(reqBuilder.build());
} catch (IOException e) {
throw new CertificateException("Failed to send request to S2A.", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new CertificateException("Failed to send request to S2A.", e);
}
if (resp.hasStatus() && resp.getStatus().getCode() != 0) {
throw new CertificateException(
String.format(
"Error occurred in response from S2A, error code: %d, error message: %s.",
resp.getStatus().getCode(), resp.getStatus().getDetails()));
}
if (!resp.hasValidatePeerCertificateChainResp()) {
throw new CertificateException("No valid response received from S2A.");
}
ValidatePeerCertificateChainResp validationResult = resp.getValidatePeerCertificateChainResp();
if (validationResult.getValidationResult()
!= ValidatePeerCertificateChainResp.ValidationResult.SUCCESS) {View on GitHub (pinned to 64daddc1f3)
Solutions
- Ensure the S2A process is running and reachable at the configured address.
- Recreate the S2AStub/channel after a ConnectionClosedException before continuing handshakes.
- Catch CertificateException in your trust-manager wrapper and fall back to a local trust store if S2A offload is optional.
Example fix
// before
resp = stub.send(req); // IOException propagates as CertificateException
// after
if (!stub.isOpen()) {
stub = stubFactory.create(channel); // rebuild before validating
}
resp = stub.send(req); Defensive patterns
Strategy: try-catch
Validate before calling
if (!isS2aProcessRunning()) {
throw new IllegalStateException("Cannot run TLS handshake: S2A process is not running");
} Try / catch
try {
sslEngine.beginHandshake();
} catch (SSLHandshakeException e) {
if (e.getCause() instanceof CertificateException
&& e.getCause().getMessage().contains("Failed to send request to S2A")) {
// transport issue to S2A, not an actual cert failure — recreate stub and retry
}
} Prevention
- Health-check the S2A process before serving TLS connections.
- Recreate stubs after stream termination instead of reusing them.
- Distinguish S2A transport failures ( cause is IOException ) from real cert failures in your error handling.
When it happens
Trigger: checkClientTrusted() or checkServerTrusted() invoked during a TLS handshake while S2AStub.send() throws IOException — closed stream, unreachable S2A, or unexpected response ( see errors 231-233 ).
Common situations: TLS peer validation with S2A offload while the S2A process is down; stale cached stub after stream close; misaddressed S2A endpoint.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
- Error occurred in response from S2A, error code: %d, error m
- Failed to get client TLS configuration from S2A.
- response from S2A server has ean error %d with error message
- TLS version %d is not supported.
- Signature Algorithm %d is not supported.
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/897543487f6d3717.
Report an issue: GitHub.