apache/iceberg · error · RESTException

Unhandled error

Error message

Unhandled error: %s

What it means

After receiving a failed HTTP response, HTTPClient passes the built ErrorResponse to the configured error handler; if that handler returns without throwing (handlers are expected to always throw for errors), this fallback RESTException is thrown so the failure is never silently swallowed. The message stringifies the ErrorResponse (status, type, message).

Solutions

  1. Read the ErrorResponse in the message to learn the actual HTTP status and server error
  2. Extend your custom ErrorHandler to throw an appropriate exception for every unhandled status
  3. Ensure your handler always terminates with a throw for any non-success response
  4. Fall back to the default handler (ErrorHandlers.defaultErrorHandler()) for statuses you don't handle

Example fix

// before
ErrorHandler handler = error -> {
  if (error.code() == 401) throw new NotAuthorizedException("reauth");
  // other codes: falls through -> 'Unhandled error'
};
// after
ErrorHandler handler = error -> {
  if (error.code() == 401) throw new NotAuthorizedException("reauth");
  defaultHandler.accept(error); // always throws for any other error
};
Defensive patterns

Strategy: try-catch

Try / catch

try {
  client.execute(request, customHandler, responseType);
} catch (RESTException e) {
  // 'Unhandled error' means the custom handler didn't throw — inspect e.getMessage()
  parseErrorResponseAndHandle(e.getMessage());
}

Prevention

When it happens

Trigger: A non-success HTTP response where the custom or default error handler's accept() completed without throwing — e.g. a custom handler that only handles certain codes (like 401 token refresh handlers that swallow or return) and an unhandled error status arrives.

Common situations: Using a partial custom ErrorHandler that omits a case for the status the server returned, or a retrying auth handler that delegates only for 401 and lets other statuses fall through to this line.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

Appendix: source

Thrown at core/src/main/java/org/apache/iceberg/rest/HTTPClient.java:245

        // without any bugs in the server implementation. So we ignore this exception and build an
        // error
        // response for the user.
        //
        // For example, the connection could time out before every reaching the server, in which
        // case we'll
        // likely get a 5xx with the load balancers default 5xx response.
        LOG.error("Failed to parse an error response. Will create one instead.", e);
      }
    }

    if (errorResponse == null) {
      errorResponse = buildDefaultErrorResponse(response);
    }

    errorHandler.accept(errorResponse);

    // Throw an exception in case the provided error handler does not throw.
    throw new RESTException("Unhandled error: %s", errorResponse);
  }

  @Override
  protected HTTPRequest buildRequest(
      HTTPMethod method,
      String path,
      Map<String, String> queryParams,
      Map<String, String> headers,
      Object body) {

    ImmutableHTTPRequest.Builder builder =
        ImmutableHTTPRequest.builder()
            .baseUri(baseUri)
            .mapper(mapper)
            .method(method)
            .path(path)
            .body(body)
            .queryParameters(queryParams == null ? Map.of() : queryParams);

View on GitHub (pinned to 86d9c8fc54)