apache/pulsar · critical · PulsarClientException.AuthenticationException
Unable to read private key: ${message}
Error message
Unable to read private key: ${message} What it means
authenticate() reads the private key file before requesting a token; any IOException from loadPrivateKey (unreadable location, bad URI, malformed wrapper) becomes PulsarClientException.AuthenticationException('Unable to read private key: <cause message>'). It signals the client never got far enough to attempt the token exchange.
Source
Thrown at pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/ClientCredentialsFlow.java:166
}
@Override
public void initialize() throws PulsarClientException {
super.initialize();
assert this.metadata != null;
URL tokenUrl = this.metadata.getTokenEndpoint();
this.exchanger = new TokenClient(tokenUrl, getHttpClient());
initialized = true;
}
public TokenResult authenticate() throws PulsarClientException {
// read the private key from storage
KeyFile keyFile;
try {
keyFile = loadPrivateKey(this.privateKey);
} catch (IOException e) {
throw new PulsarClientException.AuthenticationException("Unable to read private key: " + e.getMessage());
}
// request an access token using client credentials
ClientCredentialsExchangeRequest req = ClientCredentialsExchangeRequest.builder()
.clientId(keyFile.getClientId())
.clientSecret(keyFile.getClientSecret())
.audience(this.audience)
.scope(this.scope)
.authMethod(TokenEndpointAuthMethod.CLIENT_SECRET_POST)
.build();
TokenResult tr;
if (!initialized) {
initialize();
}
try {
tr = this.exchanger.exchangeClientCredentials(req);
} catch (TokenExchangeException | IOException e) {
throw new PulsarClientException.AuthenticationException("Unable to obtain an access token: "View on GitHub (pinned to 820761864e)
Solutions
- Verify the privateKey URL/path exists and is readable by the client process
- Validate the key file is well-formed JSON containing clientId/clientSecret
- Check file permissions or mount configuration for the secret
Example fix
// before
authParams.put("privateKey", "file:///etc/pulsar/auth/old-key.json"); // file deleted
// after
authParams.put("privateKey", "file:///etc/pulsar/auth/client_credentials.json"); // verified exists + readable Defensive patterns
Strategy: validation
Validate before calling
String key = authParams.get("privateKey");
if (key != null && key.startsWith("file:")) {
java.nio.file.Path p = java.nio.file.Paths.get(java.net.URI.create(key));
if (!java.nio.file.Files.isReadable(p)) {
throw new IllegalStateException("privateKey file not readable: " + p);
}
try (var in = java.nio.file.Files.newInputStream(p)) {
new String(in.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8).trim(); // ensure non-empty
}
} Try / catch
try {
flow.initialize();
} catch (PulsarClientException.AuthenticationException e) {
if (e.getMessage().startsWith("Unable to read private key")) {
// inspect cause: fix path/permissions/JSON, then recreate the auth
throw new ConfigException("Check privateKey path, permissions and JSON content: " + e.getMessage(), e);
}
throw e;
} Prevention
- Verify the key file path, mount and permissions in the deployment before startup
- Validate the key JSON parses (contains clientId/clientSecret) in a preflight check
- Avoid editing/rotating the key file while the client may re-read it; use atomic file replacement
When it happens
Trigger: privateKey file path does not exist or is not readable; data: URI payload is not valid JSON so KeyFile.fromJson throws and surfaces as an IO failure; permissions deny reading the secret file.
Common situations: Kubernetes secret mounted at a different path than configured; JSON key file corrupted or truncated; typo in privateKey parameter; missing IAM permissions on cloud storage backends.
Related errors
- Malformed configuration parameter: earlyTokenRefreshPercent
- Unsupported media type or encoding format: ${contentType}
- Invalid privateKey format
- Unable to obtain an access token: ${message}
- Required configuration parameter: ${name}
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/b46e9f368bf0bab7.
Report an issue: GitHub.