{"record":{"id":"b150758779751221","repo":"pentaho/pentaho-kettle","slug":"cmstokenprovider-failed-to-fetch-token-from-tokenurl-cause","errorCode":null,"errorMessage":"CmsTokenProvider: failed to fetch token from '<tokenUrl>': <cause message>","messagePattern":"CmsTokenProvider: failed to fetch token from '<tokenUrl>': <cause message>","errorType":"exception","errorClass":"KettleDatabaseException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/pentaho/di/core/database/CmsTokenProvider.java","lineNumber":178,"sourceCode":"          \"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\" );\n      if ( expiresInObj instanceof Number ) {\n        expiresInMs = ( (Number) expiresInObj ).longValue() * 1000L;\n      }\n      long validUntilMs = System.currentTimeMillis() + expiresInMs - EXPIRY_BUFFER_MS;\n\n      cached.set( new TokenEntry( accessToken, validUntilMs ) );\n      log.logDebug( \"CmsTokenProvider: token acquired, valid for ~\" + ( expiresInMs / 1000 ) + \"s\" );\n      return accessToken;\n\n    } catch ( KettleDatabaseException e ) {\n      throw e;\n    } catch ( Exception e ) {\n      throw new KettleDatabaseException(\n        \"CmsTokenProvider: failed to fetch token from '\" + tokenUrl + \"': \" + e.getMessage(), e );\n    }\n  }\n\n  /**\n   * Holds the cached access token and the absolute time (epoch ms) at which it should\n   * be considered expired for our purposes ({@code issued_at + expires_in_ms - buffer}).\n   */\n  private static final class TokenEntry {\n    final String accessToken;\n    final long validUntilMs;\n\n    TokenEntry( String accessToken, long validUntilMs ) {\n      this.accessToken = accessToken;\n      this.validUntilMs = validUntilMs;\n    }\n  }\n}","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/core/src/main/java/org/pentaho/di/core/database/CmsTokenProvider.java#L160-L196","documentation":"CmsTokenProvider wraps any unexpected exception during the token fetch (HTTP call, connection, JSON parse) into this KettleDatabaseException, embedding the tokenUrl and the underlying cause message. It signals the token exchange itself failed rather than the response merely lacking a token.","triggerScenarios":"fetchAndCache() throws this from the generic catch (Exception e) when the HTTP request to tokenUrl fails: unknown host, connection refused, TLS handshake failure, timeout, or the response body is not parseable JSON by ObjectMapper.readValue.","commonSituations":"Keycloak host unreachable or DNS misconfigured; wrong port or http vs https in tokenUrl; SSL certificate not trusted by the JVM truststore; Keycloak down; response body is HTML so Jackson parse fails.","solutions":["Test reachability: curl -v <tokenUrl> from the machine running Pentaho and fix network/DNS/proxy issues","Correct the tokenUrl scheme, host, and port (https and the right Keycloak port)","If it's a TLS error, import the Keycloak certificate into the JVM truststore (keytool -importcert)","Read the chained cause (e.getMessage() is embedded) to distinguish connection vs JSON parse failures"],"exampleFix":"// before\nString url = \"http://keycloak.internal:8180/realms/myrealm/token\";\n// after (correct OIDC token endpoint, https)\nString url = \"https://keycloak.internal:8443/realms/myrealm/protocol/openid-connect/token\";","handlingStrategy":"try-catch","validationCode":"// Reachability pre-check before fetching the token\nHttpURLConnection c = (HttpURLConnection) new URL( tokenUrl ).openConnection();\nc.setConnectTimeout( 5000 );\nif ( c.getResponseCode() < 1 ) throw new IllegalStateException( \"tokenUrl unreachable: \" + tokenUrl );","typeGuard":"boolean isValidTokenUrl( String url ) { try { new URL( url ); return url.startsWith( \"https://\" ) || url.startsWith( \"http://\" ); } catch ( MalformedURLException e ) { return false; } }","tryCatchPattern":"try { token = CmsTokenProvider.getToken(); } catch ( KettleDatabaseException e ) { // cause message embedded; classify and retry transient failures\n  if ( e.getCause() instanceof java.net.ConnectException || e.getCause() instanceof java.net.UnknownHostException ) { retryWithBackoff(); } else { throw e; } }","preventionTips":["Smoke-test the token URL with curl from the runtime host","Use https and correct Keycloak port; import TLS certs into the JVM truststore","Configure proxy settings (http.nonProxyHosts) so the Keycloak host is reachable","Distinguish transient network errors from config errors before retrying"],"tags":["keycloak","oauth2","http-request","network"],"backgroundTag":"oauth-token-exchange-failed","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"}