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

  1. Verify the token URL (scheme, host, realm, /protocol/openid-connect/token path) is correct and reachable.
  2. Check the client_id/client_secret or user credentials configured for the CMS provider against Keycloak's client/user settings.
  3. Test the same request with curl to inspect the response body — Keycloak returns 'invalid_grant', 'invalid_client', etc. with details.
  4. Confirm Keycloak and any reverse proxy are up; retry with backoff for transient 502/503/504 responses.
  5. 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

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


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)