{"record":{"id":"897543487f6d3717","repo":"grpc/grpc-java","slug":"failed-to-send-request-to-s2a","errorCode":null,"errorMessage":"Failed to send request to S2A.","messagePattern":"Failed to send request to S2A\\.","errorType":"exception","errorClass":"CertificateException","httpStatus":null,"severity":"critical","filePath":"s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2ATrustManager.java","lineNumber":154,"sourceCode":"              .addAllCertificateChain(certificateChainToDerChain(chain)));\n    } else {\n      validatePeerCertificateChainReq.setServerPeer(\n          ValidatePeerCertificateChainReq.ServerPeer.newBuilder()\n              .addAllCertificateChain(certificateChainToDerChain(chain))\n              .setServerHostname(hostname));\n    }\n\n    SessionReq.Builder reqBuilder =\n        SessionReq.newBuilder().setValidatePeerCertificateChainReq(validatePeerCertificateChainReq);\n    if (localIdentity.isPresent()) {\n      reqBuilder.setLocalIdentity(localIdentity.get().getIdentity());\n    }\n\n    SessionResp resp;\n    try {\n      resp = stub.send(reqBuilder.build());\n    } catch (IOException e) {\n      throw new CertificateException(\"Failed to send request to S2A.\", e);\n    } catch (InterruptedException e) {\n      Thread.currentThread().interrupt();\n      throw new CertificateException(\"Failed to send request to S2A.\", e);\n    }\n    if (resp.hasStatus() && resp.getStatus().getCode() != 0) {\n      throw new CertificateException(\n          String.format(\n              \"Error occurred in response from S2A, error code: %d, error message: %s.\",\n              resp.getStatus().getCode(), resp.getStatus().getDetails()));\n    }\n\n    if (!resp.hasValidatePeerCertificateChainResp()) {\n      throw new CertificateException(\"No valid response received from S2A.\");\n    }\n\n    ValidatePeerCertificateChainResp validationResult = resp.getValidatePeerCertificateChainResp();\n    if (validationResult.getValidationResult()\n        != ValidatePeerCertificateChainResp.ValidationResult.SUCCESS) {","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/s2a/src/main/java/io/grpc/s2a/internal/handshaker/S2ATrustManager.java#L136-L172","documentation":"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.","triggerScenarios":"checkClientTrusted() or checkServerTrusted() invoked during a TLS handshake while S2AStub.send() throws IOException — closed stream, unreachable S2A, or unexpected response ( see errors 231-233 ).","commonSituations":"TLS peer validation with S2A offload while the S2A process is down; stale cached stub after stream close; misaddressed S2A endpoint.","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."],"exampleFix":"// before\nresp = stub.send(req); // IOException propagates as CertificateException\n// after\nif (!stub.isOpen()) {\n  stub = stubFactory.create(channel); // rebuild before validating\n}\nresp = stub.send(req);","handlingStrategy":"try-catch","validationCode":"if (!isS2aProcessRunning()) {\n  throw new IllegalStateException(\"Cannot run TLS handshake: S2A process is not running\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  sslEngine.beginHandshake();\n} catch (SSLHandshakeException e) {\n  if (e.getCause() instanceof CertificateException\n      && e.getCause().getMessage().contains(\"Failed to send request to S2A\")) {\n    // transport issue to S2A, not an actual cert failure — recreate stub and retry\n  }\n}","preventionTips":["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."],"tags":["tls","certificate-validation","grpc","s2a","network"],"backgroundTag":"connection-refused","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}