apache/pulsar · critical · IllegalArgumentException
TlsPolicy sets certificateFilePath='certificateFilePath' but
Error message
TlsPolicy sets certificateFilePath='certificateFilePath' but leaves keyFilePath unset; a certificate without its private key yields no usable TLS identity. Set keyFilePath, or unset certificateFilePath.
What it means
TlsMaterialSource.validatePemIdentity enforces PEM config consistency: certificateFilePath set without keyFilePath cannot yield a usable TLS identity (a certificate alone is public material), so IllegalArgumentException is thrown. The inverse (key without cert) is only logged as a warning elsewhere in the same method.
Source
Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/TlsMaterialSource.java:198
+ "' holds no usable key entry (a private key with an X.509 certificate chain); no TLS "
+ "identity would be presented. Fix the keystore or its password, or unset keyStorePath.");
}
}
/**
* Reject a half-configured PEM identity that would be silently dropped. A certificate without its key
* yields {@link TlsMaterial#hasKeyMaterial()} {@code == false}, so the identity is omitted from the built
* context and the misconfiguration only surfaces as a handshake/authentication failure much later. The
* check is deliberately <em>asymmetric</em>: a key without a certificate is what v4 silently tolerated, so
* it stays a WARN rather than a new startup failure. Enforced here rather than in {@code TlsPolicy.Builder}
* so custom {@code PulsarTlsFactory} implementations that build their own policies are not constrained by
* this default factory's requirement.
*/
private void validatePemIdentity() {
boolean hasCert = StringUtils.isNotBlank(policy.certificateFilePath());
boolean hasKey = StringUtils.isNotBlank(policy.keyFilePath());
if (hasCert && !hasKey) {
throw new IllegalArgumentException("TlsPolicy sets certificateFilePath='" + policy.certificateFilePath()
+ "' but leaves keyFilePath unset; a certificate without its private key yields no usable TLS "
+ "identity. Set keyFilePath, or unset certificateFilePath.");
}
if (hasKey && !hasCert) {
log.warn().attr("keyFilePath", policy.keyFilePath())
.log("TlsPolicy sets keyFilePath but no certificateFilePath; no TLS identity will be presented");
}
}
private List<X509Certificate> loadTrustCerts() throws Exception {
if (StringUtils.isNotBlank(policy.trustStorePath())) {
List<X509Certificate> trustCerts = TlsKeyStoreLoader.extractTrustCerts(
TlsKeyStoreLoader.loadKeyStore(policy.trustStoreType(), policy.trustStorePath(),
policy.trustStorePassword(), jcaProvider));
if (trustCerts.isEmpty()) {
// An empty trust list is indistinguishable from "no truststore configured" downstream, and both
// context builders then install the platform default trust manager — silently trusting every
// public CA. v4 initialised the TrustManagerFactory with the explicit store and rejected every peer.View on GitHub (pinned to 820761864e)
Solutions
- Set keyFilePath to the private key matching certificateFilePath
- If only trust material was intended, unset certificateFilePath and configure trust material instead
- Check the config file so both certificate and key path properties are present
- After fixing paths, verify the PEM pair matches (cert and key belong together)
Example fix
// before
TlsPolicy policy = TlsPolicy.builder()
.certificateFilePath("/etc/pulsar/broker-cert.pem")
.build(); // keyFilePath missing
// after
TlsPolicy policy = TlsPolicy.builder()
.certificateFilePath("/etc/pulsar/broker-cert.pem")
.keyFilePath("/etc/pulsar/broker-key.pem")
.build(); Defensive patterns
Strategy: validation
Validate before calling
static void checkPemPair(String certPath, String keyPath) {
boolean hasCert = certPath != null && !certPath.isBlank();
boolean hasKey = keyPath != null && !keyPath.isBlank();
if (hasCert && !hasKey) throw new IllegalArgumentException("certificateFilePath set without keyFilePath");
if (!hasCert && hasKey) log.warn("keyFilePath set without certificateFilePath");
} Try / catch
try {
TlsMaterialSource.load(policy);
} catch (IllegalArgumentException e) {
log.error("PEM identity config invalid: {}", e.getMessage());
} Prevention
- Always configure certificate and key PEM paths together
- Use config templates that pair tlsCertificateFilePath with tlsKeyFilePath
- Validate PEM config symmetry at application startup
When it happens
Trigger: Calling load on a TlsMaterialSource where policy.certificateFilePath() is non-blank but policy.keyFilePath() is blank or unset, detected before any material loading.
Common situations: Partial PEM config where only the certificate path property was set in broker.conf/client config; typo'd key-file property name; a config template that omitted keyFilePath; incorrectly split server/client TLS configs.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Private key loading error
- Cross-format TLS material: tlsPolicy(...) configures a keyst
- Cross-format TLS material: tlsPolicy(...) configures a PEM t
- No TLS material configured for server purpose purpose
- Failed to set the private key
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/9b32e6030f76f9bc.
Report an issue: GitHub.