pentaho/pentaho-kettle · error · KettleDatabaseException

CmsTokenProvider: failed to fetch token from

Error message

CmsTokenProvider: failed to fetch token from '<tokenUrl>': <cause message>

What it means

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.

Solutions

  1. Test reachability: curl -v <tokenUrl> from the machine running Pentaho and fix network/DNS/proxy issues
  2. Correct the tokenUrl scheme, host, and port (https and the right Keycloak port)
  3. If it's a TLS error, import the Keycloak certificate into the JVM truststore (keytool -importcert)
  4. Read the chained cause (e.getMessage() is embedded) to distinguish connection vs JSON parse failures

Example fix

// before
String url = "http://keycloak.internal:8180/realms/myrealm/token";
// after (correct OIDC token endpoint, https)
String url = "https://keycloak.internal:8443/realms/myrealm/protocol/openid-connect/token";
Defensive patterns

Strategy: try-catch

Validate before calling

// Reachability pre-check before fetching the token
HttpURLConnection c = (HttpURLConnection) new URL( tokenUrl ).openConnection();
c.setConnectTimeout( 5000 );
if ( c.getResponseCode() < 1 ) throw new IllegalStateException( "tokenUrl unreachable: " + tokenUrl );

Type guard

boolean isValidTokenUrl( String url ) { try { new URL( url ); return url.startsWith( "https://" ) || url.startsWith( "http://" ); } catch ( MalformedURLException e ) { return false; } }

Try / catch

try { token = CmsTokenProvider.getToken(); } catch ( KettleDatabaseException e ) { // cause message embedded; classify and retry transient failures
  if ( e.getCause() instanceof java.net.ConnectException || e.getCause() instanceof java.net.UnknownHostException ) { retryWithBackoff(); } else { throw e; } }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/b150758779751221. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/org/pentaho/di/core/database/CmsTokenProvider.java:178

          "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" );
      if ( expiresInObj instanceof Number ) {
        expiresInMs = ( (Number) expiresInObj ).longValue() * 1000L;
      }
      long validUntilMs = System.currentTimeMillis() + expiresInMs - EXPIRY_BUFFER_MS;

      cached.set( new TokenEntry( accessToken, validUntilMs ) );
      log.logDebug( "CmsTokenProvider: token acquired, valid for ~" + ( expiresInMs / 1000 ) + "s" );
      return accessToken;

    } catch ( KettleDatabaseException e ) {
      throw e;
    } catch ( Exception e ) {
      throw new KettleDatabaseException(
        "CmsTokenProvider: failed to fetch token from '" + tokenUrl + "': " + e.getMessage(), e );
    }
  }

  /**
   * Holds the cached access token and the absolute time (epoch ms) at which it should
   * be considered expired for our purposes ({@code issued_at + expires_in_ms - buffer}).
   */
  private static final class TokenEntry {
    final String accessToken;
    final long validUntilMs;

    TokenEntry( String accessToken, long validUntilMs ) {
      this.accessToken = accessToken;
      this.validUntilMs = validUntilMs;
    }
  }
}

View on GitHub (pinned to f3058517a1)