apache/pulsar · error · java.lang.IllegalArgumentException

tlsFactory must not be null

Error message

tlsFactory must not be null

What it means

tlsFactory(PulsarTlsFactory) throws IllegalArgumentException when the factory argument is null. A null factory cannot be distinguished from 'not configured' once stored, and there is no other way to clear the slot, so the builder rejects null outright rather than silently unconfiguring TLS.

Source

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

        // 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);
        }
        map.put(purpose, policy);
        return this;
    }

    @Override
    public PulsarClientBuilder tlsFactory(PulsarTlsFactory factory) {
        if (factory == null) {
            throw new IllegalArgumentException("tlsFactory must not be null");
        }
        // Same rule as tlsPolicy above, and pip-478.md states it for this method by name: a factory supplies
        // material for every purpose WITHOUT enabling transport TLS. Forcing useTls here made an adopted
        // factory on a plaintext pulsar:// URL — the CLIENT_OAUTH2-only case the SPI exists to serve —
        // attempt a TLS handshake against the plaintext broker port. The adopted factory is still composed
        // and initialized: PulsarClientImpl.needsClientTlsFactory() has its own arm for conf.getTlsFactory().
        conf.setTlsFactory(factory);
        return this;
    }

    /**
     * Fold a bridged third-party v4 plugin's file-based TLS material into {@link TlsPurpose#CLIENT_DEFAULT}
     * (PIP-478). The plugin's {@code getAuthData()} has already been probed by
     * {@link #resolveGenericV4} on the application thread (off the event loop), and {@code data} is known to
     * report {@code hasDataForTls()}. Only <em>file-based</em> material (PEM cert/key file paths or a
     * keystore) can be represented in the file-path {@link TlsPolicy}; a plugin that exposes only in-memory
     * cert/key material is logged rather than silently dropped, since it cannot be folded on this path.
     *

View on GitHub (pinned to 820761864e)

Solutions

  1. Construct a valid PulsarTlsFactory (e.g. via its builder) before calling tlsFactory.
  2. Only call tlsFactory when a factory actually exists; otherwise omit the call — omitting is the correct way to have no factory.
  3. Fix the factory provider so it throws on failure instead of returning null.

Example fix

// before
builder.tlsFactory(provider.get()); // provider.get() returns null -> IllegalArgumentException
// after
PulsarTlsFactory f = provider.get();
if (f != null) {
    builder.tlsFactory(f);
}
Defensive patterns

Strategy: validation

Validate before calling

PulsarTlsFactory f = factoryProvider.get();
if (f != null) {
    builder.tlsFactory(f);
}

Type guard

static boolean hasFactory(java.util.function.Supplier<PulsarTlsFactory> s) { return s != null && s.get() != null; }

Prevention

When it happens

Trigger: Calling clientBuilder.tlsFactory(null) directly, or tlsFactory(someFactory) where a lookup/provider returned null (DI container without binding, config-driven factory creation that failed silently).

Common situations: Conditional factory construction like config.getBoolean("tls") ? buildFactory() : null; Spring/Guice optional bindings resolving to null; refactoring that changed a factory provider's failure mode from throwing to returning null.

Related errors


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