apache/iceberg · error · NotAuthorizedException

Not authorized

Error message

Not authorized: %s: %s

What it means

The OAuth2 error handler (OAuth2Client's error handler) maps an OAuth token endpoint response with error type 'invalid_client' to NotAuthorizedException. The token server rejected the client credentials — the client ID/secret or token is invalid. Message includes the OAuth error type and description.

Solutions

  1. Verify the 'credential' property format is exactly 'client-id:client-secret' and both are correct
  2. Confirm the client credentials are still active with your OAuth2/token issuer (not rotated or revoked)
  3. If using a static token, verify the token itself is valid and unexpired
  4. Check the oauth2-server-uri points at the correct token endpoint

Example fix

// before
props.put("credential", "myclient wrongsecret"); // wrong separator/secret
// after
props.put("credential", "myclient:correct-secret"); // client-id:client-secret
Defensive patterns

Strategy: validation

Validate before calling

String credential = props.get("credential");
if (credential != null && credential.indexOf(':') <= 0) {
  throw new IllegalArgumentException("credential must be 'client-id:client-secret'");
}

Try / catch

try {
  String token = OAuth2Util.fetchToken(client, authConfig);
} catch (NotAuthorizedException e) {
  throw new IllegalStateException("Invalid client credentials: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: Token exchange call to the OAuth2/token endpoint (via OAuth2Util or token fetch in the REST client) returned HTTP 400 with error=invalid_client, meaning client authentication failed.

Common situations: Wrong or rotated client secret in catalog properties, typos in 'credential' (client-id:client-secret), using a revoked client, or sending a client credential where a token is required.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/d428c7ec23be447e. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/org/apache/iceberg/rest/ErrorHandlers.java:379

  private static class OAuthErrorHandler extends ErrorHandler {
    private static final ErrorHandler INSTANCE = new OAuthErrorHandler();

    @Override
    public ErrorResponse parseResponse(int code, String json) {
      try {
        return OAuthErrorResponseParser.fromJson(code, json);
      } catch (Exception x) {
        LOG.warn("Unable to parse error response", x);
      }
      return ErrorResponse.builder().responseCode(code).withMessage(json).build();
    }

    @Override
    public void accept(ErrorResponse error) {
      if (error.type() != null) {
        switch (error.type()) {
          case OAuth2Properties.INVALID_CLIENT_ERROR:
            throw new NotAuthorizedException(
                "Not authorized: %s: %s", error.type(), error.message());
          case OAuth2Properties.INVALID_REQUEST_ERROR:
          case OAuth2Properties.INVALID_GRANT_ERROR:
          case OAuth2Properties.UNAUTHORIZED_CLIENT_ERROR:
          case OAuth2Properties.UNSUPPORTED_GRANT_TYPE_ERROR:
          case OAuth2Properties.INVALID_SCOPE_ERROR:
            throw new BadRequestException(
                "Malformed request: %s: %s", error.type(), error.message());
        }
      }
      throw createRESTException(error);
    }
  }
}

View on GitHub (pinned to 86d9c8fc54)