grpc/grpc-java · error · S2AConnectionException
Error occurred in response from S2A, error code: %d, error m
Error message
Error occurred in response from S2A, error code: %d, error message: "%s".
What it means
S2APrivateKeyMethod.sign sends a SessionReq to the S2A offload service and checks the returned SessionResp status. If S2A reports a non-zero status code, it throws S2AConnectionException carrying the S2A error code and details — meaning the offload request failed on the S2A side rather than in the local TLS stack.
Source
Thrown at s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2APrivateKeyMethod.java:132
throws IOException, InterruptedException {
checkArgument(input.length > 0, "No bytes to sign.");
SignatureAlgorithm s2aSignatureAlgorithm =
convertOpenSslSignAlgToS2ASignAlg(signatureAlgorithm);
SessionReq.Builder reqBuilder =
SessionReq.newBuilder()
.setOffloadPrivateKeyOperationReq(
OffloadPrivateKeyOperationReq.newBuilder()
.setOperation(OffloadPrivateKeyOperationReq.PrivateKeyOperation.SIGN)
.setSignatureAlgorithm(s2aSignatureAlgorithm)
.setRawBytes(ByteString.copyFrom(input)));
if (localIdentity.isPresent()) {
reqBuilder.setLocalIdentity(localIdentity.get().getIdentity());
}
SessionResp resp = stub.send(reqBuilder.build());
if (resp.hasStatus() && resp.getStatus().getCode() != 0) {
throw new S2AConnectionException(
String.format(
"Error occurred in response from S2A, error code: %d, error message: \"%s\".",
resp.getStatus().getCode(), resp.getStatus().getDetails()));
}
if (!resp.hasOffloadPrivateKeyOperationResp()) {
throw new S2AConnectionException("No valid response received from S2A.");
}
return resp.getOffloadPrivateKeyOperationResp().getOutBytes().toByteArray();
}
@Override
public byte[] decrypt(SSLEngine engine, byte[] input) {
throw new UnsupportedOperationException("decrypt is not supported.");
}
}View on GitHub (pinned to 64daddc1f3)
Solutions
- Read the error code and details in the exception message and look them up in the S2A handshaker status documentation to identify the server-side failure.
- Verify the local identity configured for the channel matches a certificate/key actually provisioned in S2A (SPIFFE ID, cert chain).
- Check S2A service logs around the failure for the corresponding request; confirm the target S2A instance is healthy.
- Add retry/backoff for transient codes and fail fast (alert) on persistent identity/key errors.
Example fix
// before
byte[] sig = keyMethod.sign(engine, input); // throws on any S2A error
// after
try {
byte[] sig = keyMethod.sign(engine, input);
} catch (S2AConnectionException e) {
if (isTransient(e)) retry(); else failHandshake(e);
} Defensive patterns
Strategy: try-catch
Try / catch
try {
byte[] sig = keyMethod.sign(engine, input);
} catch (S2AConnectionException e) {
if (isTransientCode(e)) {
retryWithBackoff();
} else {
alertAndFailHandshake(e); // persistent identity/key problem
}
} Prevention
- Provision and verify S2A identities/keys before enabling offload for a workload.
- Monitor S2A status codes and alert on persistent non-zero codes.
- Keep S2A service and client versions compatible.
- Classify error codes: retry transient ones, fail fast on identity/config errors.
When it happens
Trigger: Calling sign(engine, bytes) during an S2A-offloaded handshake when resp.hasStatus() is true and status code != 0 — e.g. S2A cannot access the private key, the identity (localIdentity) is wrong or not provisioned, the handshake context is stale, or the S2A service rejected the request.
Common situations: S2A deployment lacks the certificate/key referenced by the local identity; workload identity mismatch (wrong SPIFFE ID in s2a config); S2A and application version skew; transient S2A backend errors surfaced with a specific code.
Related errors
- TLS version %d is not supported.
- Signature Algorithm %d is not supported.
- No valid response received from S2A.
- decrypt is not supported.
- Failed to send request to S2A.
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/c4950a90cbec8644.
Report an issue: GitHub.