Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
1. Overview
Itβs 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βs often not enough to get just the response code. Sometimes we want the response body, too.
In the following examples, weβll see how we can parse the response body from the REST client WebClient. Weβll link our behavior to the status code returned, and 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) {
HttpStatusCode 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βre 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 weβll 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 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.
