Working with web flux
I have been working a lot with Webflux for the past 6 months, I have designed new services for my customers, and am always striving to deliver the best.
For most of my applications, I like to use the functional endpoint instead of the classic RestController
decorator. I can manipulate directly the server response and I feel more in control of what I am doing. The whole thing feels more concise, explicit and easy to document.
Using functional endpoints also means I have to manage all the exceptions that might be thrown from my app. Using the functionals endpoint, I can't use the classic RestControllerAdvice
to manage those exceptions. I have also seen in some blog posts that using it is a "bad practice".
I needed to find a way to manage exceptions properly for the services I am designing.
I came across multiple articles advising on extending AbstractErrorWebExceptionHandler
which does not sound the right way to do it.
How to manage the exceptions?
First, let's build a classic WebFlux app using SpringInitializr.
I am creating an interface that we can call Error
that will describe the way of returning the right error content to the client.
public interface Error {
Mono<ServerResponse> getAsServerResponse();
}
Then I will Build my Exception Type, let's say I want Business Exception and Technical Exception.
I will create them extending RuntimeException
and implementing Error
I will give them a constructor, and a function to build a response body associated to the type of error.
public class BusinessException extends RuntimeException implements Error {
private final Integer code;
private final String message;
private final String advice;
public BusinessException(Integer code, String message, String advice) {
super(message);
this.code = code;
this.message = message;
this.advice = advice;
}
@Override
public Mono<ServerResponse> getAsServerResponse() {
return ServerResponse.badRequest().bodyValue(this.createBusinessErrorMessage());
}
protected BusinessError createBusinessErrorMessage() {
return BusinessError.builder()
.message(this.message)
.advice(this.advice)
.code(this.code)
.build();
}
}
The Technical can be created the same way.
Now If I wanna create a new exception, I would just have to extend either TechnicalException
or BusinessException
. The getAsServerResponse can stay the same or I can override it individually depending on the response code I want to return.
Testing the error management.
@Bean
RouterFunction<ServerResponse> getEmployeeByIdRoute() {
return route(GET("/hello-world/{name}"),
helloController::execute);
}
I create a simple endpoint in which I will throw an error.
And here is how I manage everything at the same time.
return Mono
.fromSupplier(() -> new DummyReturn(request.pathVariable("name")))
.flatMap( dummyReturn -> ServerResponse.ok().bodyValue(dummyReturn))
.onErrorResume(BusinessException.class, BusinessException::getAsServerResponse)
.onErrorResume(TechnicalException.class, TechnicalException::getAsServerResponse);
}
Now I will just throw an error depending on random/fixed parameters
public DummyReturn(String name) {
this.name = name;
this.id = random.nextInt(20);
if (Objects.equals(name, "error")) {
throw new BusinessException(100, "Error in DummyReturn constructor", "Enter a correct name");
}
if (this.id > 10) {
throw new TechnicalException("An error happened while attributing the ID");
}
}
And here is the result
GET http://localhost:8080/hello-world/df
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 20
{
"id": 2,
"name": "df"
}
GET http://localhost:8080/hello-world/df
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
Content-Length: 56
{
"message": "An error happened while attributing the ID"
}
I love this way cause we can be very explicit without having to write a ton of code.
If you are interested in seeing more of this, the entire codebase can be found on my GitHub at https://github.com/mathias-vandaele/webflux-error-management-functional-endpoint
Thank you for reading!