apache/pulsar · error · PulsarClientException.AlreadyClosedException

Authentication already closed.

Error message

Authentication already closed.

What it means

getAuthData() is synchronized and checks the isClosed flag; once close() has been called, any attempt to fetch credentials throws AlreadyClosedException (a PulsarClientException subclass). It indicates the Authentication object's lifecycle has ended and it cannot mint or return tokens anymore.

Source

Thrown at pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/AuthenticationOAuth2.java:264

    @Override
    public void start() throws PulsarClientException {
        flow.initialize();
    }

    /**
     * The first time that this method is called, it retrieves a token. All subsequent
     * calls should get a cached value. However, if there is an issue with the Identity
     * Provider, there is a chance that the background thread responsible for keeping
     * the refresh token hot will
     * @return The authentication data identifying this client that will be sent to the broker
     * @throws PulsarClientException
     */
    @SuppressWarnings("deprecation")
    @Override
    public synchronized AuthenticationDataProvider getAuthData() throws PulsarClientException {
        if (isClosed) {
            throw new PulsarClientException.AlreadyClosedException("Authentication already closed.");
        }
        if (this.cachedToken == null || this.cachedToken.isExpired()) {
            this.authenticate();
        }
        return this.cachedToken.getAuthData();
    }

    /**
     * The IdP TLS material the configured flow carries, folded into a {@link TlsPurpose#CLIENT_OAUTH2}
     * {@link TlsPolicy} so the framework HTTP client can serve IdP mTLS / custom trust on the new PIP-478 TLS
     * path. Read at client-build / TLS-compose time (the flow is created during
     * {@link #configure}, before the client is constructed). Empty when no flow is configured or it carries
     * no IdP TLS material.
     *
     * @return the CLIENT_OAUTH2 policy, or empty
     */
    public Optional<TlsPolicy> idpTlsPolicy() {
        return idpTlsPolicy(null, null);

View on GitHub (pinned to 820761864e)

Solutions

  1. Create a fresh AuthenticationOAuth2 instance instead of reusing a closed one
  2. Guard with authentication state checks before calling getAuthData()
  3. Ensure the client that owns the authentication is not closed while the token is needed

Example fix

// before
client.close();
AuthenticationDataProvider d = auth.getAuthData(); // throws
// after
AuthenticationDataProvider d = auth.getAuthData();
client.close();
Defensive patterns

Strategy: try-catch

Validate before calling

// guard on lifecycle before use
if (!authLifecycleOpen) {
    throw new IllegalStateException("Authentication closed; recreate before getAuthData()");
}

Try / catch

try {
    AuthenticationDataProvider data = auth.getAuthData();
} catch (PulsarClientException.AlreadyClosedException e) {
    auth = createNewAuthentication(); // recreate and retry once
    AuthenticationDataProvider data = auth.getAuthData();
}

Prevention

When it happens

Trigger: Calling getAuthData() (directly or via currentAccessToken) after authentication.close(); using a cached Authentication instance whose client was shut down; tests reusing a closed auth object.

Common situations: Application shutdown racing with in-flight token refresh; sharing one Authentication instance across multiple clients and closing one of them; reconnect logic firing after client teardown.

Understand the failure class

Related errors


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