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
- 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
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
- 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
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
- CmsTokenProvider: Keycloak response did not contain…
- CmsTokenProvider: Keycloak token request failed — HTTP
- An error occurred sending a slave transformation:
- An error occurred sending the master transformation:
- Cannot load WSDL file: + _wsdlName
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)