apache/pulsar · error · java.lang.IllegalArgumentException
Cross-format TLS material: tlsPolicy(...) configures a PEM t
Error message
Cross-format TLS material: tlsPolicy(...) configures a PEM truststore (trustCertsFilePath) but the authentication plugin supplies a keystore client certificate/key. Folding these would silently drop the configured truststore. Configure the trust material and the client identity in the same format (both PEM, or both keystore).
What it means
Mirror of the PEM-side guard: when the auth plugin supplies a keystore client identity (AuthenticationKeyStoreTls or a generic plugin exposing KeyStoreParams), the builder folds it into a KEYSTORE-format CLIENT_DEFAULT policy. If the configured tlsPolicy instead carries a PEM truststore (trustCertsFilePath), folding would drop those trust anchors, so build() fails loudly with this IllegalArgumentException.
Source
Thrown at pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5.java:520
}
/** Copy the trust material and flags of {@code base} (if any) into a keystore-format builder. */
private static TlsPolicy.Builder keyStoreBuilder(TlsPolicy base) {
TlsPolicy.Builder b = copyFlags(base).format(TlsPolicy.Format.KEYSTORE);
if (base != null && base.format() == TlsPolicy.Format.KEYSTORE) {
// Preserve the base truststore (path, password, and TYPE): folding the auth plugin's keystore must
// not clobber the truststore type configured via tlsPolicy(...) — the keystore and truststore may
// use different types (e.g. a PKCS12 keystore with a JKS truststore).
b.trustStorePath(base.trustStorePath())
.trustStorePassword(base.trustStorePassword())
.trustStoreType(base.trustStoreType());
} else if (base != null && isNotBlank(base.trustCertsFilePath())) {
// Cross-format fold: the tlsPolicy(...) carries a PEM truststore (trustCertsFilePath) but the auth
// plugin's client identity is a keystore. A keystore policy has no PEM trust field, so folding here
// would silently drop the configured trust anchors and fall back to the system trust store. Fail loud
// (matching TlsPolicy.build()'s fail-loud format validation) rather than silently broadening/breaking
// trust.
throw new IllegalArgumentException("Cross-format TLS material: tlsPolicy(...) configures a PEM "
+ "truststore (trustCertsFilePath) but the authentication plugin supplies a keystore client "
+ "certificate/key. Folding these would silently drop the configured truststore. Configure the "
+ "trust material and the client identity in the same format (both PEM, or both keystore).");
}
return b;
}
private static TlsPolicy.Builder copyFlags(TlsPolicy base) {
TlsPolicy.Builder b = TlsPolicy.builder();
if (base != null) {
b.allowInsecureConnection(base.allowInsecureConnection())
.enableHostnameVerification(base.enableHostnameVerification())
.protocols(base.protocols())
.ciphers(base.ciphers())
// Preserve the pinned JSSE (SSLContext) provider across the fold. A FIPS deployment
// pins it via tlsPolicy(...); dropping it here would let the transport fall back to
// the default JDK engine, silently defeating the pin.
.jsseProvider(base.jsseProvider())View on GitHub (pinned to 820761864e)
Solutions
- Import the PEM CA bundle into a keystore (keytool -importcert) and configure trustStorePath/trustStorePassword/trustStoreType on the tlsPolicy instead of trustCertsFilePath.
- Or switch the client identity to PEM (AuthenticationTls with cert/key file paths) so both sides are PEM.
- Remove trustCertsFilePath if the keystore already contains the needed trust anchors and system/default trust is intended.
- Verify which format each side uses before building: policy.format() vs the plugin type.
Example fix
// before
builder.tlsPolicy(TlsPolicy.builder().format(PEM).trustCertsFilePath("ca.pem").build())
.authentication(new AuthenticationKeyStoreTls(ksParams)); // IllegalArgumentException at build()
// after
builder.tlsPolicy(TlsPolicy.builder().format(KEYSTORE)
.trustStorePath("truststore.jks").trustStorePassword(pw).trustStoreType("JKS").build())
.authentication(new AuthenticationKeyStoreTls(ksParams)); Defensive patterns
Strategy: validation
Validate before calling
// Before build(), when using a keystore auth plugin with a tlsPolicy:
TlsPolicy p = clientPolicy;
boolean keystorePlugin = authPlugin instanceof AuthenticationKeyStoreTls;
if (keystorePlugin && p != null && p.format() == TlsPolicy.Format.PEM
&& p.trustCertsFilePath() != null && !p.trustCertsFilePath().isBlank()) {
throw new IllegalStateException("Use trustStorePath (keystore) with a keystore auth plugin");
} Try / catch
try {
client = builder.build();
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Cross-format TLS material")) {
// import the PEM CA into a keystore or switch the plugin to PEM, then rebuild
throw new IllegalStateException("Align trust + identity TLS formats (both PEM or both keystore)", e);
}
throw e;
} Prevention
- keytool -importcert the PEM CA bundle into your truststore when using keystore identity.
- Decide the format once per deployment and enforce it in configuration templates.
- Build the client in a startup smoke test to catch mismatches early.
- Remember keystore and truststore types may differ (PKCS12 keystore + JKS truststore) — that is fine; the format axis is what must match.
When it happens
Trigger: Calling tlsPolicy(policy) with a PEM-format policy that sets trustCertsFilePath, AND configuring a keystore-based auth plugin (AuthenticationKeyStoreTls with KeyStoreParams, or a generic v4 plugin exposing getTlsKeyStoreParams()), then calling build().
Common situations: The common Java deployment uses a JKS/PKCS12 client keystore but the trust anchors were given as a PEM CA bundle (e.g. copied from a curl/openssl setup or a Kubernetes CA cert); teams migrating from system-trust defaults then adding one side in the other format.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Cross-format TLS material: tlsPolicy(...) configures a keyst
- Failed to acquire TLS material for purpose ${purpose}
- TlsPolicy field '${field}' is set but is not valid for forma
- The replication cluster does not provide TLS encrypted servi
- No usable service URL (useTls=${useTls}, serviceUrl=${servic
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/5e324644ec09097b.
Report an issue: GitHub.