{"record":{"id":"ab9f3721331bc9eb","repo":"apache/pulsar","slug":"cross-format-tls-material-tlspolicy-configur","errorCode":null,"errorMessage":"Cross-format TLS material: tlsPolicy(...) configures a keystore truststore (trustStorePath) but the authentication plugin supplies a PEM 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).","messagePattern":"Cross-format TLS material: tlsPolicy\\(\\.\\.\\.\\) configures a keystore truststore \\(trustStorePath\\) but the authentication plugin supplies a PEM 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\\)\\.","errorType":"exception","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5.java","lineNumber":496,"sourceCode":"\n    private void mergeClientDefault(ClientConfigurationData target,\n                                    java.util.function.Function<TlsPolicy, TlsPolicy> merge) {\n        Map<TlsPurpose, TlsPolicy> map = target.getTlsPolicyMap();\n        TlsPolicy base = map.get(TlsPurpose.CLIENT_DEFAULT);\n        map.put(TlsPurpose.CLIENT_DEFAULT, merge.apply(base));\n    }\n\n    /** Copy the trust material and flags of {@code base} (if any) into a PEM-format builder. */\n    private static TlsPolicy.Builder pemBuilder(TlsPolicy base) {\n        TlsPolicy.Builder b = copyFlags(base).format(TlsPolicy.Format.PEM);\n        if (base != null && base.format() == TlsPolicy.Format.PEM) {\n            b.trustCertsFilePath(base.trustCertsFilePath());\n        } else if (base != null && isNotBlank(base.trustStorePath())) {\n            // Cross-format fold: the tlsPolicy(...) carries a keystore truststore but the auth plugin's client\n            // identity is PEM. A PEM policy has no truststore field, so folding here would silently drop the\n            // configured trust anchors and fall back to the system trust store. Fail loud (matching\n            // TlsPolicy.build()'s fail-loud format validation) rather than silently broadening/breaking trust.\n            throw new IllegalArgumentException(\"Cross-format TLS material: tlsPolicy(...) configures a keystore \"\n                    + \"truststore (trustStorePath) but the authentication plugin supplies a PEM client \"\n                    + \"certificate/key. Folding these would silently drop the configured truststore. Configure the \"\n                    + \"trust material and the client identity in the same format (both PEM, or both keystore).\");\n        }\n        return b;\n    }\n\n    /** Copy the trust material and flags of {@code base} (if any) into a keystore-format builder. */\n    private static TlsPolicy.Builder keyStoreBuilder(TlsPolicy base) {\n        TlsPolicy.Builder b = copyFlags(base).format(TlsPolicy.Format.KEYSTORE);\n        if (base != null && base.format() == TlsPolicy.Format.KEYSTORE) {\n            // Preserve the base truststore (path, password, and TYPE): folding the auth plugin's keystore must\n            // not clobber the truststore type configured via tlsPolicy(...) — the keystore and truststore may\n            // use different types (e.g. a PKCS12 keystore with a JKS truststore).\n            b.trustStorePath(base.trustStorePath())\n                    .trustStorePassword(base.trustStorePassword())\n                    .trustStoreType(base.trustStoreType());\n        } else if (base != null && isNotBlank(base.trustCertsFilePath())) {","sourceCodeStart":478,"sourceCodeEnd":514,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5.java#L478-L514","documentation":"At build() time the builder folds the authentication plugin's client certificate/key (PEM files) into the configured CLIENT_DEFAULT TlsPolicy. If that policy was configured with a keystore truststore (trustStorePath), the fold would drop the configured trust anchors — a PEM policy has no truststore field — so the library fails loudly with this IllegalArgumentException instead of silently falling back to the system trust store.","triggerScenarios":"Calling tlsPolicy(policy) with a KEYSTORE-format policy that sets trustStorePath, AND configuring a PEM-based auth plugin (AuthenticationTls with cert/key file paths, or a generic v4 plugin exposing PEM cert/key via getAuthData()), then calling build().","commonSituations":"Mixed TLS configuration where ops provided a JKS/PKCS12 truststore for broker verification but the app uses AuthenticationTls with PEM cert/key files; migrating a v4 client config where these were separate settings and never cross-checked; copying TLS settings from a keystore-based service into a PEM-based client setup.","solutions":["Make trust and identity formats consistent: replace trustStorePath with trustCertsFilePath(...) (PEM CA bundle) on the tlsPolicy, matching the PEM client identity.","Alternatively switch the auth plugin to a keystore-based one (AuthenticationKeyStoreTls) so identity is keystore-format like the truststore.","Split trust domains if genuinely needed: configure trust via the keystore policy but supply the client identity through the same keystore policy rather than a PEM plugin.","If the truststore was unintentional, remove trustStorePath from the policy so the fold completes without dropping trust material."],"exampleFix":"// before\nbuilder.tlsPolicy(TlsPolicy.builder().format(KEYSTORE).trustStorePath(\"truststore.jks\").build())\n       .authentication(AuthenticationFactory.tls(\"cert.pem\", \"key.pem\")); // IllegalArgumentException at build()\n// after\nbuilder.tlsPolicy(TlsPolicy.builder().format(PEM)\n        .trustCertsFilePath(\"ca-cert.pem\").build())\n       .authentication(AuthenticationFactory.tls(\"cert.pem\", \"key.pem\"));","handlingStrategy":"validation","validationCode":"// Before build(), when using a PEM auth plugin with a tlsPolicy:\nTlsPolicy p = clientPolicy; // your configured CLIENT_DEFAULT policy\nboolean pemPlugin = usesPemIdentity(authPlugin);\nif (pemPlugin && p != null && p.format() == TlsPolicy.Format.KEYSTORE\n        && p.trustStorePath() != null && !p.trustStorePath().isBlank()) {\n    throw new IllegalStateException(\"Use trustCertsFilePath (PEM) with a PEM auth plugin\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    client = builder.build();\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Cross-format TLS material\")) {\n        throw new IllegalStateException(\"Align trust + identity TLS formats (both PEM or both keystore)\", e);\n    }\n    throw e;\n}","preventionTips":["Standardize one TLS material format (PEM recommended) across truststore and client identity.","If trust anchors arrive as JKS/PKCS12, convert to PEM (keytool -exportcert / openssl) for PEM-based setups.","Keep a startup smoke test that builds the client so format mismatches surface at boot, not first connect.","Document which format your ops team distributes."],"tags":["pulsar","tls","pem","keystore","configuration-conflict"],"backgroundTag":"cross-format-tls-material","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}