apache/iceberg · error · ForbiddenException
Forbidden
Error message
Forbidden: %s
What it means
RESTClient's default error handler maps HTTP 403 responses to ForbiddenException. Authentication succeeded but the server refused to authorize the operation for the given principal — the user lacks permission on the resource.
Solutions
- Inspect the server message to identify the denied resource and required privilege
- Grant the authenticated principal the required permission on the namespace/table via your catalog's authorization system
- Verify you are connecting with the intended credentials/role, not a shared or default account
- If the operation should be allowed, check for policy misconfiguration (e.g. wrong namespace ownership)
Example fix
// before
// user has only READ on namespace 'prod'
catalog.loadTable("prod.events").refresh(); // ok
catalog.dropTable("prod.events"); // Forbidden: 403
// after
// run drop with a principal granted TABLE_DROP on prod.events, or
// request the privilege: GRANT DROP ON TABLE prod.events TO ROLE etl; Defensive patterns
Strategy: try-catch
Try / catch
try {
catalog.dropTable(identifier);
} catch (ForbiddenException e) {
log.warn("Permission denied for {}: {}", principal, e.getMessage());
} Prevention
- Grant the service account the exact privileges needed for each operation
- Test permission setup with a dry-run in a dev namespace
- Audit IAM/policy changes that could remove grants mid-job
When it happens
Trigger: HTTP 403 returned by the REST server for any catalog operation (create/drop/rename table, namespace operations) where the authenticated identity lacks the required privilege.
Common situations: Service account without write access attempting commits, namespace-level ACLs blocking table creation, IAM/policy changes removing grants, or attempting admin-only operations with a read-only role.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Cannot call commit on temporary table operations
- Cannot call refresh on temporary table operations
- Delete failed for
- Failed to close HTTP client
- Failed to convert HTTP response body to string
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/256e8b4d63d8b690.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/apache/iceberg/rest/ErrorHandlers.java:345
return ErrorResponseParser.fromJson(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) {
switch (error.code()) {
case 400:
if (IllegalArgumentException.class.getSimpleName().equals(error.type())) {
throw new IllegalArgumentException(error.message());
}
throw new BadRequestException("Malformed request: %s", error.message());
case 401:
throw new NotAuthorizedException("Not authorized: %s", error.message());
case 403:
throw new ForbiddenException("Forbidden: %s", error.message());
case 405:
case 406:
break;
case 500:
throw new ServiceFailureException("Server error: %s: %s", error.type(), error.message());
case 501:
throw new UnsupportedOperationException(error.message());
case 503:
throw new ServiceUnavailableException("Service unavailable: %s", error.message());
}
throw createRESTException(error);
}
}
private static class OAuthErrorHandler extends ErrorHandler {
private static final ErrorHandler INSTANCE = new OAuthErrorHandler();
View on GitHub (pinned to 86d9c8fc54)