apache/pulsar · error · java.lang.IllegalArgumentException

tlsPolicy purpose and policy must not be null

Error message

tlsPolicy purpose and policy must not be null

What it means

tlsPolicy(purpose, policy) validates its arguments and throws IllegalArgumentException when either the TlsPurpose or the TlsPolicy is null. The overload tlsPolicy(TlsPolicy) forwards TlsPurpose.CLIENT_DEFAULT, so this error from that path means the policy itself was null. It is a fail-fast guard instead of a NullPointerException deep inside map handling.

Source

Thrown at pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/PulsarClientBuilderV5.java:402

        return this;
    }

    @Override
    public PulsarClientBuilder transactionPolicy(TransactionPolicy policy) {
        conf.setEnableTransaction(true);
        this.transactionTimeout = policy.timeout();
        return this;
    }

    @Override
    public PulsarClientBuilder tlsPolicy(TlsPolicy policy) {
        return tlsPolicy(TlsPurpose.CLIENT_DEFAULT, policy);
    }

    @Override
    public PulsarClientBuilder tlsPolicy(TlsPurpose purpose, TlsPolicy policy) {
        if (purpose == null || policy == null) {
            throw new IllegalArgumentException("tlsPolicy purpose and policy must not be null");
        }
        // useTls governs the BINARY BROKER TRANSPORT only, so a policy for a different trust domain must not
        // turn it on. Configuring CLIENT_OAUTH2 (the identity provider) against a plaintext pulsar:// broker
        // is a legitimate combination — it was enabling TLS toward the broker and failing the connection.
        // The policy map itself is what triggers TLS-factory creation, so a non-transport purpose still gets
        // its factory without touching the transport.
        //
        // CLIENT_DEFAULT alone, not every client-role purpose: pip-478.md calls this "one narrow addition",
        // being the only v5 expression of the legacy client.conf useTls=true with a plain pulsar:// URL.
        // BROKER_CLIENT and plugin-minted purposes (TlsPurpose.client("...")) are client-role too, and
        // enabling the transport for them is the same defect this guard was added to fix.
        if (TlsPurpose.CLIENT_DEFAULT.equals(purpose)) {
            conf.setUseTls(true);
        }
        Map<TlsPurpose, TlsPolicy> map = conf.getTlsPolicyMap();
        if (map == null) {
            map = new LinkedHashMap<>();
            conf.setTlsPolicyMap(map);

View on GitHub (pinned to 820761864e)

Solutions

  1. Pass a real TlsPolicy built via TlsPolicy.builder()...build().
  2. Check the source variable for null before calling tlsPolicy and skip or default the TLS configuration when absent.
  3. If only the purpose may vary, ensure you pass a valid TlsPurpose constant (e.g. TlsPurpose.CLIENT_DEFAULT) or a purpose from TlsPurpose.client(name).

Example fix

// before
TlsPolicy policy = loadFromConfig(); // may be null
builder.tlsPolicy(TlsPurpose.CLIENT_DEFAULT, policy); // IllegalArgumentException if null
// after
TlsPolicy policy = loadFromConfig();
if (policy != null) {
    builder.tlsPolicy(TlsPurpose.CLIENT_DEFAULT, policy);
}
Defensive patterns

Strategy: validation

Validate before calling

if (purpose != null && policy != null) {
    builder.tlsPolicy(purpose, policy);
}

Type guard

static boolean isConfigurableTls(TlsPurpose p, TlsPolicy t) { return p != null && t != null; }

Prevention

When it happens

Trigger: Calling clientBuilder.tlsPolicy(null) or clientBuilder.tlsPolicy(purpose, null), or tlsPolicy(somePurpose, policy) where somePurpose is null; typically the result of a variable that failed to initialize or a configuration lookup that returned null.

Common situations: Loading TlsPolicy from config/properties where a missing key yields null; a factory method returning null on invalid input; refactoring code where the purpose enum constant was removed or renamed.

Related errors


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