{"record":{"id":"bb4f0ba89ebbae1e","repo":"apache/pulsar","slug":"unable-to-obtain-an-access-token-message","errorCode":null,"errorMessage":"Unable to obtain an access token: ${message}","messagePattern":"Unable to obtain an access token: (.+?)","errorType":"exception","errorClass":"PulsarClientException.AuthenticationException","httpStatus":null,"severity":"critical","filePath":"pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/ClientCredentialsFlow.java","lineNumber":184,"sourceCode":"            throw new PulsarClientException.AuthenticationException(\"Unable to read private key: \" + e.getMessage());\n        }\n\n        // request an access token using client credentials\n        ClientCredentialsExchangeRequest req = ClientCredentialsExchangeRequest.builder()\n                .clientId(keyFile.getClientId())\n                .clientSecret(keyFile.getClientSecret())\n                .audience(this.audience)\n                .scope(this.scope)\n                .authMethod(TokenEndpointAuthMethod.CLIENT_SECRET_POST)\n                .build();\n        TokenResult tr;\n        if (!initialized) {\n            initialize();\n        }\n        try {\n            tr = this.exchanger.exchangeClientCredentials(req);\n        } catch (TokenExchangeException | IOException e) {\n            throw new PulsarClientException.AuthenticationException(\"Unable to obtain an access token: \"\n                    + e.getMessage());\n        }\n\n        return tr;\n    }\n\n    @Override\n    public void close() throws Exception {\n        super.close();\n        if (exchanger != null) {\n            exchanger.close();\n        }\n    }\n}\n","sourceCodeStart":166,"sourceCodeEnd":199,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/oauth2/ClientCredentialsFlow.java#L166-L199","documentation":"After loading the key, authenticate() performs the OAuth2 client-credentials token exchange. TokenExchangeException or IOException from exchanger.exchangeClientCredentials is wrapped as AuthenticationException('Unable to obtain an access token: <cause message>'). This means the key was read but the token request against the identity provider failed.","triggerScenarios":"IdP rejects credentials (invalid_client), the token endpoint URL is unreachable (DNS/TLS error), or the metadata-resolved token endpoint returned an error; network outage between client and IdP.","commonSituations":"Expired or rotated client secret not updated in config; wrong issuerUrl so the wrong token endpoint is used; firewall/proxy blocking the POST; clock skew or audience misconfiguration on the IdP.","solutions":["Check the cause message: fix clientId/clientSecret if invalid_client","Verify issuerUrl is correct and the IdP is reachable from the client network","Confirm the OAuth2 app is enabled and the token endpoint allows client_credentials grant"],"exampleFix":"// before\nauthParams.put(\"clientSecret\", \"stale-secret\"); // rotated on IdP\n// after\nauthParams.put(\"clientSecret\", \"<newly-rotated-secret>\");","handlingStrategy":"retry","validationCode":"// preflight: ensure IdP token endpoint is reachable\ntry {\n    HttpURLConnection c = (HttpURLConnection) new java.net.URI(issuerUrl).toURL().openConnection();\n    c.setConnectTimeout(5000);\n    c.connect();\n} catch (Exception e) {\n    throw new IllegalStateException(\"IdP unreachable: \" + e.getMessage());\n}","typeGuard":null,"tryCatchPattern":"try {\n    flow.initialize();\n} catch (PulsarClientException.AuthenticationException e) {\n    if (e.getMessage().startsWith(\"Unable to obtain an access token\")) {\n        if (e.getMessage().contains(\"invalid_client\")) {\n            refreshClientCredentials(); // rotate secret and retry\n        } else {\n            backoffAndRetry(); // transient network error\n        }\n    } else throw e;\n}","preventionTips":["Keep clientId/clientSecret in sync with IdP rotations","Monitor IdP availability and network path from client hosts","Validate the issuerUrl resolves to the correct tenant/realm before deploying"],"tags":["oauth2","network","auth","token-exchange"],"backgroundTag":"token-exchange-failed","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}