Skip to content
Advertisement

How / where to check for java.net.http.HttpClient HTTP response status codes

Java 11’s java.net.http.HttpClient does not seem to check the HTTP status code, except for redirects (if enabled). And all examples found in the wiki and in the Java API documentation always assume that HTTP requests are successful, i.e. they never seem to check the status code of the response.

This is most likely never the desired behavior because an error page from an HTTP 500 (server error) response has probably a different, or no format and can therefore not be handled by the application.

Where should the check for the HTTP status code occur?

The documentation of HttpResponse.BodyHandler contains the following example snippet:

BodyHandler<Path> bodyHandler = (rspInfo) -> rspInfo.statusCode() == 200
    ? BodySubscribers.ofFile(Paths.get("/tmp/f"))
    : BodySubscribers.replacing(Paths.get("/NULL"));

However, then you would have to check twice for the status code, once in the BodyHandler shown above, and once when handling the response (since trying to read the body from "/NULL" would fail).

To me it seems most reasonable to perform the HTTP status code check in the BodyHandler only, e.g.:

BodyHandler<Path> bodyHandler = (rspInfo) -> {
    if (rspInfo.statusCode() == 200) {
        return BodySubscribers.ofFile(Paths.get("/tmp/f"));
    } else {
        throw new RuntimeException("Request failed");
    }
};

However, the BodyHandler documentation does not mention whether it is allowed to throw exceptions, or how HttpClient would behave in that case.

It is also surprises me that the JDK does not seem to offer functionality for handling unsuccessful HTTP responses out of the box, or am I overlooking something?

Advertisement

Answer

The HttpClient attempts to do a good job at catching exceptions thrown by user code – but this is not the recommended way of dealing with non 200 status. You’d be relying on unspecified behavior (though it would probably do what you expect).

If you want to return an exception in case of a status which is != 200, then my recommendation would be to write a body subscriber that:

  1. Return a failed CompletionStage (which you would have completed exceptionally with your exception)
  2. Cancel the subscription (or forwards it to a BodySubscribers.discarding() subscriber)

On the other hand – if you want a different result type for the case where status != 200 you could write a BodyHandler that returns a tuple (response, error) where response is of one type and error is of another type. Something like this:

record Response<R,T>(R response, T error) {}
static class ErrorBodyHandler<R,T> implements BodyHandler<Response<R,T>> {
    final BodyHandler<R> responseHandler;
    final BodyHandler<T> errorHandler;
    public ErrorBodyHandler(BodyHandler<R> responseHandler, BodyHandler<T> errorHandler) {
        this.responseHandler = responseHandler;
        this.errorHandler = errorHandler;
    }
    @Override
    public BodySubscriber<Response<R, T>> apply(ResponseInfo responseInfo) {
        if (responseInfo.statusCode() == 200) {
            return BodySubscribers.mapping(responseHandler.apply(responseInfo),
                    (r) -> new Response<>(r, null));
        } else {
            return BodySubscribers.mapping(errorHandler.apply(responseInfo),
                    (t) -> new Response<>(null, t));
        }
    }
}

public static void main(String[] args) throws Exception {
    var client = HttpClient.newHttpClient();
    var handler = new ErrorBodyHandler<>(BodyHandlers.ofFileDownload(Path.of(".")),
            BodyHandlers.ofString());
    var request = HttpRequest
            .newBuilder(URI.create("http://host:port/"))
            .build();
    var httpResponse = 
            client.send(request, handler);
    if (httpResponse.statusCode() == 200) {
        Path path = httpResponse.body().response();
    } else {
        String error = httpResponse.body().error();
    }
}
User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement