pentaho/pentaho-kettle · critical · HttpException

Unable to get authorization token " +…

Error message

Unable to get authorization token " + response.getStatusLine().toString()

What it means

In Mail.getOauthToken(), the OAuth2 token endpoint responded with a non-200 status; an HttpException with the full status line is thrown and wrapped in a RuntimeException. This means the client could not obtain the bearer token needed for SMTP XOAUTH2 authentication.

Solutions

  1. Inspect the wrapped cause in the RuntimeException for the exact HTTP status line and fix credentials (client id, secret, refresh token, redirect URI) in the Mail step's OAuth settings.
  2. Re-generate the refresh token following your provider's OAuth consent flow (e.g. Google OAuth Playground for Gmail).
  3. Verify network/proxy reachability of the token endpoint from the Pentaho server.
  4. Catch the RuntimeException in calling code and surface a clear 'check OAuth configuration' message instead of failing the transformation silently.

Example fix

// before
authenticationToken = "${OAUTH_REFRESH}"; // expired refresh token
// after
authenticationToken = "1//0abc..."; // freshly generated refresh token, redirect_uri matches app registration
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: verify OAuth config fields are non-empty before sending
if (Utils.isEmpty(oauthUsername) || Utils.isEmpty(authenticationToken)) {
  throw new KettleException("OAuth settings incomplete: username and refresh token are required.");
}

Try / catch

try { sendMail(...); } catch (RuntimeException e) {
  Throwable c = e.getCause();
  logError("OAuth token acquisition failed (check client id/secret, refresh token, redirect URI): " + (c != null ? c.getMessage() : e.getMessage()), e);
}

Prevention

When it happens

Trigger: HTTP POST to the OAuth token URL returns status != 200 (invalid client_id/client_secret/refresh_token, wrong redirect_uri, expired token), or the response body cannot be parsed into EmailAuthenticationResponse (IOException).

Common situations: Expired or revoked refresh token; wrong OAuth tenant/redirect URI configured in the Mail step; network/proxy blocking the token endpoint; Gmail OAuth scope changes after Google policy updates.

Related errors


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

Appendix: source

Thrown at plugins/mail/impl/src/main/java/org/pentaho/di/trans/steps/mail/Mail.java:790

      tokenUrl = environmentSubstitute( meta.getTokenUrl() );
      HttpPost httpPost = new HttpPost( tokenUrl );
      List<NameValuePair> form = new ArrayList<>();
      form.add( new BasicNameValuePair( "scope", environmentSubstitute( meta.getScope() ) ) );
      form.add( new BasicNameValuePair( "client_id", environmentSubstitute( meta.getClientId() ) ) );
      form.add( new BasicNameValuePair( "client_secret", environmentSubstitute( meta.getSecretKey() ) ) );
      form.add( new BasicNameValuePair( "grant_type", environmentSubstitute( meta.getGrant_type() ) ) );
      if ( meta.getGrant_type().equals( MailMeta.GRANTTYPE_REFRESH_TOKEN ) ) {
        form.add( new BasicNameValuePair( MailMeta.GRANTTYPE_REFRESH_TOKEN, environmentSubstitute( meta.getRefresh_token() ) ) );
      }
      if ( meta.getGrant_type().equals( MailMeta.GRANTTYPE_AUTHORIZATION_CODE ) ) {
        form.add( new BasicNameValuePair( "code", environmentSubstitute( meta.getAuthorization_code() ) ) );
        form.add( new BasicNameValuePair( "redirect_uri", environmentSubstitute( meta.getRedirectUri() ) ) );
      }
      UrlEncodedFormEntity entity = new UrlEncodedFormEntity( form, Consts.UTF_8 );
      httpPost.setEntity( entity );
      try ( CloseableHttpResponse response = client.execute( httpPost ) ) {
        if ( response.getStatusLine().getStatusCode() != HttpStatus.SC_OK ) {
          throw new HttpException( "Unable to get authorization token " + response.getStatusLine().toString() );
        }
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue( EntityUtils.toString( response.getEntity() ), EmailAuthenticationResponse.class );
      } catch ( HttpException | IOException e ) {
        throw new RuntimeException( e );
      }
    } catch ( IOException e ) {
      throw new RuntimeException( e );
    }
  }

  @VisibleForTesting
  void setAttachedFiles( MailMeta meta, Object[] r, LogChannelInterface log ) throws Exception {
    if ( meta.isDynamicFilename() || meta.isZipFilenameDynamic() ) {
      setAttachedFilesList( r, log );
    } else {
      setAttachedFilesList( null, log );
    }

View on GitHub (pinned to f3058517a1)