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

  1. Import the PEM CA bundle into a keystore (keytool -importcert) and configure trustStorePath/trustStorePassword/trustStoreType on the tlsPolicy instead of trustCertsFilePath.
  2. Or switch the client identity to PEM (AuthenticationTls with cert/key file paths) so both sides are PEM.
  3. Remove trustCertsFilePath if the keystore already contains the needed trust anchors and system/default trust is intended.
  4. 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

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

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/5e324644ec09097b. Report an issue: GitHub.