pentaho/pentaho-kettle · critical · HttpException

Unable to get authorization token

Error message

Unable to get authorization token 

What it means

In JobEntryMail's getOauthToken, the OAuth2 token endpoint returned a non-200 status, throwing HttpException with the status line, wrapped in RuntimeException. The job could not obtain the bearer token for XOAUTH2 SMTP authentication.

Solutions

  1. Unwrap the RuntimeException cause to see the HTTP status and correct the OAuth credentials in the job entry.
  2. Regenerate the refresh token via your provider's OAuth flow and update the job entry.
  3. Confirm the token endpoint is reachable from the job executor (proxy/firewall/DNS).
  4. Catch the RuntimeException around getOauthToken and fail with an actionable message.

Example fix

// before
redirectUri = "http://localhost:8080/callback"; // not registered with the OAuth app
// after
redirectUri = "https://app.example.com/oauth2/callback"; // matches registered redirect URI
Defensive patterns

Strategy: try-catch

Validate before calling

if (Utils.isEmpty(oauthUsername) || Utils.isEmpty(authenticationToken)) {
  throw new KettleException("OAuth settings incomplete for mail job entry.");
}

Try / catch

try { execute(); } catch (RuntimeException e) {
  Throwable c = e.getCause();
  logError("OAuth token exchange failed; verify credentials/redirect URI/network: " + (c != null ? c.getMessage() : e.getMessage()), e);
}

Prevention

When it happens

Trigger: HTTP POST to the token URL returns non-200 (invalid/ expired refresh token, wrong client id/secret/redirect URI) or the token JSON cannot be read (IOException).

Common situations: Expired refresh token after the OAuth app's consent was revoked; mismatched redirect_uri vs the app registration; proxy/firewall blocking the token endpoint from the job server.

Related errors


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

Appendix: source

Thrown at plugins/mail-job/impl/src/main/java/org/pentaho/di/job/entries/mail/JobEntryMail.java:1526

      this.tokenUrl = environmentSubstitute( tokenUrl );
      HttpPost httpPost = new HttpPost( this.tokenUrl );
      List<NameValuePair> form = new ArrayList<>();
      form.add( new BasicNameValuePair( "scope", environmentSubstitute( scope ) ) );
      form.add( new BasicNameValuePair( "client_id", environmentSubstitute( clientId ) ) );
      form.add( new BasicNameValuePair( "client_secret", environmentSubstitute( secretKey ) ) );
      form.add( new BasicNameValuePair( "grant_type", environmentSubstitute( grant_type ) ) );
      if ( grant_type.equals( JobEntryMail.GRANTTYPE_REFRESH_TOKEN ) ) {
        form.add( new BasicNameValuePair( JobEntryMail.GRANTTYPE_REFRESH_TOKEN, environmentSubstitute( refresh_token ) ) );
      }
      if ( grant_type.equals( JobEntryMail.GRANTTYPE_AUTHORIZATION_CODE ) ) {
        form.add( new BasicNameValuePair( "code", environmentSubstitute( authorization_code ) ) );
        form.add( new BasicNameValuePair( "redirect_uri", environmentSubstitute( redirectUri ) ) );
      }
      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 );
    }
  }

  @Override
  public List<ResourceReference> getResourceDependencies( JobMeta jobMeta ) {
    List<ResourceReference> references = super.getResourceDependencies( jobMeta );
    String realServername = jobMeta.environmentSubstitute( server );
    ResourceReference reference = new ResourceReference( this );
    reference.getEntries().add( new ResourceEntry( realServername, ResourceType.SERVER ) );
    references.add( reference );

View on GitHub (pinned to f3058517a1)