apache/pulsar · critical · IllegalStateException

Failed to acquire TLS material for purpose ${purpose}

Error message

Failed to acquire TLS material for purpose ${purpose}

What it means

JettyTlsFactory.awaitAcquisition blocks until TLS key/certificate material (the native Jetty SslContextFactory) has been resolved for a given 'purpose'. If the asynchronous acquisition completes exceptionally, RuntimeExceptions/Errors are rethrown as-is; anything else is wrapped in this IllegalStateException. It means the broker could not obtain or build its TLS context (bad keystore, unreadable key file, failed decryption), so TLS listeners cannot start.

Source

Thrown at pulsar-broker-common/src/main/java/org/apache/pulsar/jetty/tls/JettyTlsFactory.java:472

     * so a configuration failure surfaces as its own cause. The underlying messages ("No TLS material
     * configured for server purpose WEB") are the actionable ones and should not arrive wrapped.
     *
     * @param pending the acquisition
     * @param purpose the purpose being acquired, for the failure message
     * @return the acquisition result
     */
    private static <T> T awaitAcquisition(CompletableFuture<T> pending, TlsPurpose purpose) {
        try {
            return pending.join();
        } catch (CompletionException e) {
            Throwable cause = e.getCause() != null ? e.getCause() : e;
            if (cause instanceof RuntimeException runtime) {
                throw runtime;
            }
            if (cause instanceof Error error) {
                throw error;
            }
            throw new IllegalStateException("Failed to acquire TLS material for purpose " + purpose, cause);
        }
    }

    /**
     * Overlay a factory-supplied engine baseline onto a synthesized server factory (PIP-478 merge order):
     * enabled protocols/cipher suites when the companion sets them, and the companion's client-auth mode as
     * authoritative (rule 4) — {@code needClientAuth} wins over {@code wantClientAuth}, neither means none.
     */
    private static void applyServerBaseline(SslContextFactory.Server sslContextFactory, SSLParameters baseline) {
        if (baseline.getProtocols() != null) {
            sslContextFactory.setIncludeProtocols(baseline.getProtocols());
        }
        if (baseline.getCipherSuites() != null) {
            sslContextFactory.setIncludeCipherSuites(baseline.getCipherSuites());
        }
        if (baseline.getNeedClientAuth()) {
            sslContextFactory.setNeedClientAuth(true);
        } else if (baseline.getWantClientAuth()) {

View on GitHub (pinned to 820761864e)

Solutions

  1. Inspect the 'cause' of the IllegalStateException — it names the real TLS failure (bad password, missing file, invalid keystore).
  2. Verify the keystore/certificate and key file paths exist and are readable by the broker process user.
  3. Confirm the keystore password and type (JKS/PKCS12) match the actual file; for PEM, check tlsKeyFilePath/tlsCertificateFilePath/tlsTrustCertsFilePath values.
  4. Validate the certificate/key pair and chain (openssl x509 / keytool -list) and renew if expired.
  5. Ensure the TLS provider configuration (e.g. OpenSSL vs JDK provider) is consistent with the supplied material.

Example fix

// before (broker.conf)
tlsCertificateFilePath=/missing/cert.pem
tlsKeyFilePath=/missing/key.pem
// after
tlsCertificateFilePath=/etc/pulsar/tls/broker.cert.pem
tlsKeyFilePath=/etc/pulsar/tls/broker.key.pem
# chmod 600 /etc/pulsar/tls/broker.key.pem
Defensive patterns

Strategy: validation

Validate before calling

// Validate TLS material before broker startup
java.nio.file.Path cert = java.nio.file.Path.of(conf.getTlsCertificateFilePath());
java.nio.file.Path key = java.nio.file.Path.of(conf.getTlsKeyFilePath());
if (!java.nio.file.Files.isReadable(cert)) throw new IllegalStateException("Unreadable cert: " + cert);
if (!java.nio.file.Files.isReadable(key)) throw new IllegalStateException("Unreadable key: " + key);
if (conf.getTlsKeyStorePassword() != null) {
    try (var in = java.nio.file.Files.newInputStream(java.nio.file.Path.of(conf.getTlsKeyStore()))) {
        var ks = java.security.KeyStore.getInstance("PKCS12");
        ks.load(in, conf.getTlsKeyStorePassword().toCharArray());
    } catch (Exception e) {
        throw new IllegalStateException("Keystore cannot be loaded: " + e.getMessage(), e);
    }
}

Try / catch

try {
    factory.acquireNativeJettyFactory(purpose).get();
} catch (java.util.concurrent.ExecutionException e) {
    throw new IllegalStateException("TLS material invalid for purpose " + purpose + ": " + e.getCause(), e.getCause());
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

Prevention

When it happens

Trigger: Calling acquireNativeJettyFactory / subscription / resolveBaselineParameters when the underlying acquisition future failed with a non-RuntimeException cause: KeyStore load failure, UnrecoverableKeyException, FileNotFoundException on the keystore/truststore path, wrong key-store password, or unsupported certificate format.

Common situations: Misconfigured tlsCertificateFilePath/tlsKeyFilePath or keystore paths in broker.conf; wrong keystore password; expired/invalid certificates; file permissions preventing the broker user from reading the TLS material; switching TLS providers (PEM vs JKS/PKCS12) without matching config.

Understand the failure class

Related errors


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