12.4.4 Handling errors
All of the WebClient examples thus far have assumed a happy ending; there were no responses with 400-level or 500-level status codes. Should either kind of error statuses be returned, WebClient will log the failure and move on without incident.
If you need to handle such errors, then a call to onStatus() can be used to specify how various HTTP status codes should be dealt with. onStatus() accepts two functions: a predicate function, which is used to match the HTTP status, and a function that, given a ClientResponse object, returns a Mono<Throwable>.
To demonstrate how onStatus() can be used to create a custom error handler, consider the following use of WebClient that aims to fetch an ingredient given its ID:
Mono<Ingredient> ingredientMono = webClient
.get()
.uri("http://localhost:8080/ingredients/{id}", ingredientId)
.retrieve()
.bodyToMono(Ingredient.class);As long as the value in ingredientId matches a known ingredient resource, then the resulting Mono will publish the Ingredient object when it’s subscribed to. But what would happen if there were no matching ingredient?
When subscribing to a Mono or Flux that might end in an error, it’s important to register an error consumer as well as a data consumer in the call to subscribe() as follows:
ingredientMono.subscribe(
ingredient -> {
// handle the ingredient data
...
},
error-> {
// deal with the error
...
});If the ingredient resource is found, then the first lambda (the data consumer) given to subscribe() is invoked with the matching Ingredient object. But if it isn’t found, then the request responds with a status code of HTTP 404 (NOT FOUND), which results in the second lambda (the error consumer) being given by default a WebClientResponseException.
The biggest problem with WebClientResponseException is that it’s rather nonspecific as to what may have gone wrong to cause the Mono to fail. Its name suggests that there was an error in the response from a request made by WebClient, but you’ll need to dig into WebClientResponseException to know what went wrong. And in any event, it would be nice if the exception given to the error consumer were more domain-specific instead of WebClient-specific.
By adding a custom error handler, you can provide code that translates a status code to a Throwable of your own choosing. Let’s say that you want a failed request for an ingredient resource to cause the Mono to complete in error with an UnknownIngredientException. You can add the following call to onStatus() after the call to retrieve() to achieve that:
Mono<Ingredient> ingredientMono = webClient
.get()
.uri("http://localhost:8080/ingredients/{id}", ingredientId)
.retrieve()
.onStatus(HttpStatus::is4xxClientError,
response -> Mono.just(new UnknownIngredientException()))
.bodyToMono(Ingredient.class);The first argument in the onStatus() call is a predicate that’s given an HttpStatus and returns true if the status code is one you want to handle. And if the status code matches, then the response will be returned to the function in the second argument to handle as it sees fit, ultimately returning a Mono of type Throwable.
In the example, if the status code is a 400-level status code (e.g., a client error), then a Mono will be returned with an UnknownIngredientException. This causes the ingredientMono to fail with that exception.
Note that HttpStatus::is4xxClientError is a method reference to the is4xxClientError method of HttpStatus. It’s this method that will be invoked on the given HttpStatus object. If you want, you can use another method on HttpStatus as a method reference; or you can provide your own function in the form of a lambda or method reference that returns a boolean.
For example, you can get even more precise in your error handling, checking specifically for an HTTP 404 (NOT FOUND) status by changing the call to onStatus() to look like this::
Mono<Ingredient> ingredientMono = webClient
.get()
.uri("http://localhost:8080/ingredients/{id}", ingredientId)
.retrieve()
.onStatus(status -> status == HttpStatus.NOT_FOUND,
response -> Mono.just(new UnknownIngredientException()))
.bodyToMono(Ingredient.class);It’s also worth noting that you can have as many calls to onStatus() as you need to handle any variety of HTTP status codes that might come back in the response.
