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
- Create a fresh AuthenticationOAuth2 instance instead of reusing a closed one
- Guard with authentication state checks before calling getAuthData()
- 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
- Do not share one Authentication instance across independently closed clients
- Close authentication only after all token consumers are done
- Track closed state centrally and route getAuthData calls through a lifecycle manager
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- ServiceUrlProvider has already been initialized
- earlyTokenRefreshPercent must be greater than 0.
- Required configuration parameters: tlsCertFile, tlsKeyFile
- Unsupported auth method: ${tokenEndpointAuthMethod}
- EarlyTokenRefreshPercent must be greater than 0.
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/8b6e404ba1077868.
Report an issue: GitHub.