apache/pulsar · critical · TlsMaterialUnavailableException

No TLS material configured for server purpose purpose

Error message

No TLS material configured for server purpose purpose

What it means

The TLS factory resolves a material source for a TlsPurpose. If no registered source applies and the purpose role is SERVER (clients fall back to systemDefaultSource), no server-side certificate identity is configured, so TlsMaterialUnavailableException is thrown — the server could not present any TLS identity.

Source

Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/tls/impl/FileBasedTlsFactory.java:420

        }
    }

    /**
     * Resolve a requested purpose to the {@link RegisteredSource} that owns its material, applying the
     * role's terminal-resolution rule when nothing is configured for the purpose.
     *
     * @throws TlsMaterialUnavailableException when a server-role purpose has no material configured
     */
    private RegisteredSource resolve(TlsPurpose purpose) {
        Objects.requireNonNull(purpose, "purpose must not be null");
        RegisteredSource source = registry.get(purpose);
        if (source != null) {
            return source;
        }
        if (purpose.role() == TlsPurpose.Role.CLIENT) {
            return systemDefaultSource();
        }
        throw new TlsMaterialUnavailableException(
                "No TLS material configured for server purpose " + purpose);
    }

    private RegisteredSource systemDefaultSource() {
        RegisteredSource existing = this.systemDefaultSource;
        if (existing != null) {
            return existing;
        }
        synchronized (this) {
            if (this.systemDefaultSource == null) {
                // System default: verify hostnames, OS trust store, no client certificate. A constant
                // source (no files, never rotates).
                TlsPolicy defaultPolicy = TlsPolicy.builder().build();
                this.systemDefaultSource = new RegisteredSource(
                        TlsPurpose.CLIENT_DEFAULT, defaultPolicy, null);
            }
            return this.systemDefaultSource;
        }

View on GitHub (pinned to 820761864e)

Solutions

  1. Configure server identity: set keyStorePath (with password) or certificateFilePath + keyFilePath in TlsPolicy
  2. Ensure the policy object wired into the factory is the one holding the server TLS settings
  3. Use a client-role TlsPurpose if this path should fall back to system defaults
  4. Register a custom material source for the purpose if provisioning programmatically

Example fix

// before
TlsPolicy policy = TlsPolicy.builder().tlsEnabled(true).build(); // no server identity
// after
TlsPolicy policy = TlsPolicy.builder()
    .tlsEnabled(true)
    .keyStorePath("/etc/pulsar/broker.keystore.jks")
    .keyStorePassword("********")
    .build();
Defensive patterns

Strategy: validation

Validate before calling

boolean hasServerIdentity(TlsPolicy p) {
    return p.keyStorePath() != null || (p.certificateFilePath() != null && p.keyFilePath() != null);
}
// check before constructing a server-role TlsPurpose

Try / catch

try {
    factory.createInstance(serverPurpose, SslContext.class).join();
} catch (TlsMaterialUnavailableException e) {
    log.error("Server TLS identity not configured: {}", e.getMessage());
}

Prevention

When it happens

Trigger: Building a server-role TLS instance via createInstance/createOneShot while TlsPolicy has neither keyStorePath nor certificateFilePath/keyFilePath set, and no other registered material source matches the purpose.

Common situations: Broker/proxy TLS listeners enabled (tlsEnabled=true) without a server keystore or PEM pair configured; a policy copy that dropped server settings; using a server-role purpose with a client-only policy.

Understand the failure class

Related errors


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