WebClient Error Handling made Easy

Sudharshan S.R.
Nerd For Tech
Published in
4 min readOct 14, 2021

--

Many of the readers might be familiar with WebClient and its various usages, but just for explanation sake, let me reiterate the obvious ;).

What is WebClient ?

It was introduced as part of Spring Reactive web module, thus providing an alternative for RestTemplate in scenarios where you are working with a reactive stack. But don’t be mistaken!. We can use the same for normal functionalities , where you need to wait for the service call to be over to proceed with the remaining functionality. This is called a blocking operation.
Thus WebClient can support both synchronous and non-synchronous operations.

How To handle Errors?

The assumption here is, the reader understands the basics of webclient and how to use it, thus I will directly jump into error handling part. Do let me know if you guys need to know how to start working with a webclient.
Ok so let’s begin. There are various ways in which you can handle errors while using a webclient. I’ll explain the easiest ones and let you guys do the exploring for other possible ways.

Handling the Errors, Literally

But before we jump into the approaches, always remember to extend RunTimeException to which ever exception you are defining, specific to the usage within WebClient. This makes things a lot easier.

public class UserDefinedException extends RuntimeException {//your class definition which includes error attributes... etc}
  • While Initialising WebClient

We can define a ExchangeFilterFunction which will encapsulate errors based on the associated error status code.

public static ExchangeFilterFunction errorHandler() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
if (clientResponse.statusCode().is5xxServerError()) {
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new UserDefinedException1(errorBody)));
} else if (clientResponse.statusCode().is4xxClientError()) {
return clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new UserDefinedException2(errorBody)));
} else {
return Mono.just(clientResponse);
}
});
}

As mentioned in the code block, whenever a 5XX/4XX Error occurs, we can throw a user defined exception, and then execute error handling logic based on those user defined exceptions. Once this error Handler is defined, we can add it in the WebClient Initialisation.

WebClient webClient() {
return WebClient
.builder()
.filter(errorHandler())
.baseUrl("baseURL")
.build();
}

So now wherever this webclient is used, automatically all the errors would be wrapped with the customised exception, that we have declared in the previous steps.

  • While Executing Service Call

The previous approach was more of a generic way but, there may be scenarios where we need to handle only specific error codes. In such cases we can implement error handling logic, while executing the webclient, as mentioned below

try {
String response = webClient.get()
.retrieve()
.onStatus(httpStatus -> httpStatus.value() == <desired Status code>,
error -> Mono.error(new UserDefinedException("error Body")))
.bodyToMono(String.class)
.block();
} catch(UserDefinedException userDefinedException) {
//exception handling logic
}

In the above mentioned snippet, we can replace whichever status we want and handle the error accordingly. The good thing is you can have more than one onStatus(), thus you can handle more than one status code as per your business requirement.

  • Lets do some Retries ;)

The scenarios covered till now are basic and direct scenarios but actually the usual flow that is mostly followed when an error occurs is

  1. Based on the error, do specific number of retries
  2. After the retries are exhausted, throw a specific error.
try {
UserDefinedResponse response = webClient
.post()
.body(Mono.just(userDefinedRequest), Map.class)
.retrieve()
.bodyToMono(UserDefinedResponse.class)
.onErrorResume(Mono::error)
.retryWhen(Retry.backoff(3, Duration.of(2, ChronoUnit.SECONDS))
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) ->
new UserDefinedException(retrySignal.failure())))
.block();
} catch(UserDefinedException userDefinedException){
//Error Handling logic
}

The above mentioned code snippet shows how to achieve exponential retry using webclient. “Retry.backoff(3, Duration.of(2, ChronoUnit.SECONDS)” , this line encapsulates the needed logic for retry.

  • 3 denotes the maximum number of retries that are to be executed before implementing the exhaustion logic
  • 2 seconds denotes the minimum time before the first backoff kicks in.

Once the retries are exhausted onRetryExhaustedThrow() gets executed and thus we can handle the error as per our business logic.

The End

This is just an overview of sorts, and there are various other possibilities to explore. When working on spring boot API development, I got stuck while handling errors using WebClient, thus I wanted to record all the approaches that I had taken to navigate through error handling. Suggestions and feedbacks are always welcome, so please do add your approach or insight, as in how you find these approaches compared to ones you already know. :). Happy Coding people !!!.

--

--

Sudharshan S.R.
Nerd For Tech

Programmer, Data Enthusiast, Software Engineer By profession , Avid Anime Fan, New Tech Fanatic, Foodie