Generic Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

It is often helpful to use the status code from an HTTP response to determine what an application should do next with the given response.

In this tutorial, we'll look at how to access the status code, and response body returned from a REST request using WebFlux's WebClient.

WebClient was introduced in Spring 5 and can be used for asynchronous I/O while calling RESTful services.

2. Use Case

When making RESTful calls to other services, applications typically use the returned status code to trigger different functionality. Typical use cases include graceful error handling, triggering retries of the request, and determining user error.

As such, when making REST calls, it is often not enough to get just the response code. Sometimes we want the response body too.

In the following examples, let's see how we may parse the response body from the REST client WebClient. We'll link our behavior to the status code returned and will make use of two methods of status code extraction provided by WebClient: onStatus and ExchangeFilterFunction.

3. Using onStatus

onStatus is a built-in mechanism that can be used to handle a WebClient response. This allows us to apply finely grained functionality based on specific responses (such as 400, 500, 503, etc.) or on categories of statuses (such as 4XX, and 5XX, etc.):

WebClient
  .builder()
  .build()
  .post()
  .uri("/some-resource")
  .retrieve()
  .onStatus(
    HttpStatus.INTERNAL_SERVER_ERROR::equals,
    response -> response.bodyToMono(String.class).map(Exception::new))

The onStatus method requires two parameters. The first is a predicate that takes in a status code. Execution of the second parameter is based on the output of the first. The second is a function that maps the response to a Mono or an Exception.

In this case, if we see an INTERNAL_SERVER_ERROR (i.e., 500), we'll take the body, using bodyToMono, and then map that to a new Exception.

We can chain onStatus calls to be able to provide functionality for different status conditions:

Mono<String> response = WebClient
  .builder()
  .build()
  .post()
  .uri("some-resource")
  .retrieve()
  .onStatus( 
    HttpStatus.INTERNAL_SERVER_ERROR::equals,
    response -> response.bodyToMono(String.class).map(CustomServerErrorException::new)) 
  .onStatus(
    HttpStatus.BAD_REQUEST::equals,
    response -> response.bodyToMono(String.class).map(CustomBadRequestException::new))
  ... 
  .bodyToMono(String.class);

// do something with response

Now onStatus calls map to our custom exceptions. We defined exception types for each of the two error statuses. The onStatus method allows us to use any type we choose.

4. Using ExchangeFilterFunction

An ExchangeFilterFunction is another way to handle specific status codes and get response bodies. Unlike onStatus, the exchange filter is flexible and applies to filter functionality based on any boolean expression.

We can benefit from the flexibility of an ExchangeFilterFunction to cover the same categories as the onStatus function.

First, we'll define a method to handle the returned logic based on the status code given a ClientResponse:

private static Mono<ClientResponse> exchangeFilterResponseProcessor(ClientResponse response) {
    HttpStatus status = response.statusCode();
    if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
        return response.bodyToMono(String.class)
          .flatMap(body -> Mono.error(new CustomServerErrorException(body)));
    }
    if (HttpStatus.BAD_REQUEST.equals(status)) {
        return response.bodyToMono(String.class)
          .flatMap(body -> Mono.error(new CustomBadRequestException(body)));
    }
    return Mono.just(response);
}

Next, we'll define the filter and use a method reference to our handler:

ExchangeFilterFunction errorResponseFilter = ExchangeFilterFunction
  .ofResponseProcessor(WebClientStatusCodeHandler::exchangeFilterResponseProcessor);

Similar to the onStatus calls, we are mapping to our Exception types on error. However, using Mono.error will wrap this Exception in a ReactiveException. This nesting should be kept in mind when handling the error.

Now, let's apply this to an instance of a WebClient to achieve the same effect as the onStatus chained call:

Mono<String> response = WebClient
  .builder()
  .filter(errorResponseFilter)
  .build()
  .post()
  .uri("some-resource")
  .retrieve()
  .bodyToMono(String.class);

// do something with response

5. Conclusion

In this article, we've covered a couple of the methods to get the response body based on the HTTP status header. Based on the status code, the onStatus method allows us to plug specific functionality. In addition, we can use the filter method to plug in a general-purpose method to handle post-processing on all responses.

As always, all the code in this tutorial can be found over on GitHub.

Generic bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!