grpc/grpc-java · error · CertificateException
Peer certificate(s) missing
Error message
Peer certificate(s) missing
What it means
Thrown by XdsX509TrustManager.verifySubjectAltNameInChain when SAN verification is configured (certContext present and the match list is non-empty) but the peer presented an empty certificate chain (null or zero-length X509Certificate[]). This is a guard before extracting the leaf cert: SAN checking cannot proceed without at least one peer certificate.
Source
Thrown at xds/src/main/java/io/grpc/xds/internal/security/trust/XdsX509TrustManager.java:226
// at this point there's no match
throw new CertificateException("Peer certificate SAN check failed");
}
/**
* Verifies SANs in the peer cert chain against verify_subject_alt_name in the certContext.
* This is called from various check*Trusted methods.
*/
@VisibleForTesting
void verifySubjectAltNameInChain(X509Certificate[] peerCertChain,
List<StringMatcher> verifyList) throws CertificateException {
if (certContext == null) {
return;
}
if (verifyList.isEmpty()) {
return;
}
if (peerCertChain == null || peerCertChain.length < 1) {
throw new CertificateException("Peer certificate(s) missing");
}
// verify SANs only in the top cert (leaf cert)
verifySubjectAltNameInLeaf(peerCertChain[0], verifyList);
}
@Override
@SuppressWarnings("deprecation") // gRFC A29 predates match_typed_subject_alt_names
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
throws CertificateException {
chooseDelegate(chain).checkClientTrusted(chain, authType, socket);
verifySubjectAltNameInChain(chain, certContext != null
? certContext.getMatchSubjectAltNamesList() : new ArrayList<>());
}
@Override
@SuppressWarnings("deprecation") // gRFC A29 predates match_typed_subject_alt_names
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine)
throws CertificateException {View on GitHub (pinned to 64daddc1f3)
Solutions
- Ensure the peer actually sends a certificate: configure its keystore/keyCertChain and private key correctly so the TLS stack includes a client/server cert in the handshake
- On the server, set needClientAuth (not wantClientAuth) so clients without certs are rejected earlier with a clearer error
- If the peer legitimately has no certificate, remove match_subject_alt_names from the cert context so SAN checking is skipped
- Confirm the matching trust manager (chooseDelegate) and keystore on the peer side are loaded — a missing cert provider yields an empty chain
Example fix
// before: optional client auth allows empty chains sslContext.getDefaultSSLParameters().setWantClientAuth(true); // after: require a client certificate sslContext.getDefaultSSLParameters().setNeedClientAuth(true);
Defensive patterns
Strategy: validation
Validate before calling
// Reject empty peer chains before trust-manager processing
static void requirePeerChain(X509Certificate[] chain) throws CertificateException {
if (chain == null || chain.length == 0) {
throw new CertificateException("Peer did not present a certificate");
}
} Type guard
static boolean peerChainPresent(X509Certificate[] chain) {
return chain != null && chain.length > 0;
} Try / catch
try {
serverCall();
} catch (SSLHandshakeException e) {
if (e.getCause() instanceof CertificateException
&& e.getCause().getMessage().contains("Peer certificate(s) missing")) {
log.error("Peer sent no certificate during mTLS handshake; check client keystore/needClientAuth");
}
throw e;
} Prevention
- Use setNeedClientAuth(true) on servers requiring mTLS so empty chains fail early
- Verify the peer's keystore contains a valid key + cert chain at startup (health check)
- Enable javax.net.debug=ssl:handshake to see whether a cert is actually sent
When it happens
Trigger: checkClientTrusted or checkServerTrusted is invoked by the JDK SSL stack with chain == null or chain.length == 0 while a non-empty match_subject_alt_names list is set in the cert context. In practice this happens when the peer sends no certificate (anonymous or empty client auth) yet SAN validation is expected.
Common situations: Client certificate is optional on the server but the xDS config enables SAN verification; peer's TLS layer failed to select a cert (no matching key/cert in keystore) and sent an empty chain; misconfigured keystores on the peer so it sends no cert during mTLS handshake.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Peer certificate SAN check failed
- Failed to extract SPIFFE ID from peer leaf certificate
- Spiffe Trust Map doesn't contain trust domain '%s' from peer
- ${validationResult.getValidationDetails()} (server-provided
- tls_certificate_provider_instance is required in downstream-
AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08).
Data as JSON: /api/errors/8fa15741e9dee45b.
Report an issue: GitHub.