{"record":{"id":"567bec26b3859ed5","repo":"pentaho/pentaho-kettle","slug":"cmstokenprovider-keycloak-token-request-failed-http-status","errorCode":null,"errorMessage":"CmsTokenProvider: Keycloak token request failed — HTTP <status> from <tokenUrl>","messagePattern":"CmsTokenProvider: Keycloak token request failed — HTTP <status> from <tokenUrl>","errorType":"exception","errorClass":"KettleDatabaseException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/pentaho/di/core/database/CmsTokenProvider.java","lineNumber":147,"sourceCode":"    log.logDebug( \"CmsTokenProvider: fetching bearer token from \" + tokenUrl );\n\n    String body = \"grant_type=client_credentials\"\n      + \"&client_id=\" + clientId\n      + \"&client_secret=\" + clientSecret;\n\n    HttpClientManager manager = HttpClientManager.getInstance();\n    try ( var client = manager.createDefaultClient() ) {\n      var request = new HttpPost( tokenUrl );\n\n      request.addHeader( \"Content-Type\", \"application/x-www-form-urlencoded\" );\n      request.addHeader( \"Accept\", \"application/json\" );\n      request.setEntity( new StringEntity( body, StandardCharsets.UTF_8 ) );\n\n      var response = client.execute( request );\n\n      int status = response.getStatusLine().getStatusCode();\n      if ( status != HttpURLConnection.HTTP_OK ) {\n        throw new KettleDatabaseException(\n          \"CmsTokenProvider: Keycloak token request failed — HTTP \" + status\n            + \" from \" + tokenUrl );\n      }\n\n      Map<?, ?> responseBody;\n      try ( java.io.InputStream is = response.getEntity().getContent() ) {\n        responseBody = new ObjectMapper().readValue( is, Map.class );\n      }\n\n      Object tokenObj = responseBody.get( \"access_token\" );\n      if ( tokenObj == null ) {\n        throw new KettleDatabaseException(\n          \"CmsTokenProvider: Keycloak response did not contain 'access_token'\" );\n      }\n      String accessToken = tokenObj.toString();\n\n      long expiresInMs = 300_000L; // default 5 min if field is absent\n      Object expiresInObj = responseBody.get( \"expires_in\" );","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/core/src/main/java/org/pentaho/di/core/database/CmsTokenProvider.java#L129-L165","documentation":"CmsTokenProvider.fetchAndCache sends a token request (e.g. OAuth2 password/client-credentials grant) to a Keycloak token endpoint via HttpClient and requires HTTP 200. Any other status code is converted into a KettleDatabaseException that includes the HTTP status and the token URL, aborting token acquisition and caching.","triggerScenarios":"getToken() -> fetchAndCache() where the Keycloak POST returns non-200: wrong realm/token URL, invalid client_id/client_secret, expired or bad user credentials, Keycloak down behind a proxy returning 502/503, or network middleware returning 407.","commonSituations":"Keycloak realm renamed or removed after a version upgrade; client credentials rotated without updating Kettle config; reverse proxy auth in front of Keycloak; TLS-terminating LB returning 503 during Keycloak restarts.","solutions":["Verify the token URL (scheme, host, realm, /protocol/openid-connect/token path) is correct and reachable.","Check the client_id/client_secret or user credentials configured for the CMS provider against Keycloak's client/user settings.","Test the same request with curl to inspect the response body — Keycloak returns 'invalid_grant', 'invalid_client', etc. with details.","Confirm Keycloak and any reverse proxy are up; retry with backoff for transient 502/503/504 responses.","Catch KettleDatabaseException around getToken() and implement a refresh/re-authentication flow when this error occurs."],"exampleFix":"// before\nprops.setProperty(\"cms.token.url\", \"https://sso.example.com/auth/realms/old-realm/token\");\n\n// after\nprops.setProperty(\"cms.token.url\",\n  \"https://sso.example.com/realms/new-realm/protocol/openid-connect/token\");","handlingStrategy":"retry","validationCode":"HttpURLConnection c = (HttpURLConnection) new URL(tokenUrl).openConnection();\nc.setRequestMethod(\"HEAD\");\nif (c.getResponseCode() >= 400) {\n  throw new IllegalStateException(\"Token endpoint unreachable/incorrect: HTTP \" + c.getResponseCode());\n}","typeGuard":null,"tryCatchPattern":"try {\n  String token = CmsTokenProvider.getToken();\n} catch (KettleDatabaseException e) {\n  // non-200 from Keycloak: inspect status in message, retry with backoff or re-auth\n  LOG.error(\"Keycloak token request failed; check credentials/realm URL\", e);\n  throw new AuthenticationException(e);\n}","preventionTips":["Keep the Keycloak realm and token endpoint path in config and verify after Keycloak upgrades (path changed in Keycloak 17+: /realms/<r>/protocol/openid-connect/token).","Rotate client secrets in step with Keycloak client configuration.","Implement retry with backoff for 502/503/504 from proxies.","Test the token request with curl before deploying configuration changes."],"tags":["network","http","keycloak","authentication","oauth"],"backgroundTag":"http-error-response","analyzedSha":"f3058517a153da500bf4551f46d79b91bf8ec552","analyzedAt":"2026-09-13T14:04:16.340Z","contentChangedAt":"2026-09-13T14:04:16.340Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}