pentaho/pentaho-kettle · error · KettleDatabaseException
CmsTokenProvider: Keycloak token request failed — HTTP
Error message
CmsTokenProvider: Keycloak token request failed — HTTP <status> from <tokenUrl>
What it means
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.
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.
Example fix
// before
props.setProperty("cms.token.url", "https://sso.example.com/auth/realms/old-realm/token");
// after
props.setProperty("cms.token.url",
"https://sso.example.com/realms/new-realm/protocol/openid-connect/token"); Defensive patterns
Strategy: retry
Validate before calling
HttpURLConnection c = (HttpURLConnection) new URL(tokenUrl).openConnection();
c.setRequestMethod("HEAD");
if (c.getResponseCode() >= 400) {
throw new IllegalStateException("Token endpoint unreachable/incorrect: HTTP " + c.getResponseCode());
} Try / catch
try {
String token = CmsTokenProvider.getToken();
} catch (KettleDatabaseException e) {
// non-200 from Keycloak: inspect status in message, retry with backoff or re-auth
LOG.error("Keycloak token request failed; check credentials/realm URL", e);
throw new AuthenticationException(e);
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Unable to get authorization token
- Unable to get authorization token
- Unable to get authorization token " +…
- Auth error
- Carte.Error.NoServerFound
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/567bec26b3859ed5.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/pentaho/di/core/database/CmsTokenProvider.java:147
log.logDebug( "CmsTokenProvider: fetching bearer token from " + tokenUrl );
String body = "grant_type=client_credentials"
+ "&client_id=" + clientId
+ "&client_secret=" + clientSecret;
HttpClientManager manager = HttpClientManager.getInstance();
try ( var client = manager.createDefaultClient() ) {
var request = new HttpPost( tokenUrl );
request.addHeader( "Content-Type", "application/x-www-form-urlencoded" );
request.addHeader( "Accept", "application/json" );
request.setEntity( new StringEntity( body, StandardCharsets.UTF_8 ) );
var response = client.execute( request );
int status = response.getStatusLine().getStatusCode();
if ( status != HttpURLConnection.HTTP_OK ) {
throw new KettleDatabaseException(
"CmsTokenProvider: Keycloak token request failed — HTTP " + status
+ " from " + tokenUrl );
}
Map<?, ?> responseBody;
try ( java.io.InputStream is = response.getEntity().getContent() ) {
responseBody = new ObjectMapper().readValue( is, Map.class );
}
Object tokenObj = responseBody.get( "access_token" );
if ( tokenObj == null ) {
throw new KettleDatabaseException(
"CmsTokenProvider: Keycloak response did not contain 'access_token'" );
}
String accessToken = tokenObj.toString();
long expiresInMs = 300_000L; // default 5 min if field is absent
Object expiresInObj = responseBody.get( "expires_in" );View on GitHub (pinned to f3058517a1)