Search code examples
spring-bootspring-webfluxspring-webclientwebflux

Example of error handling calling Restful services with WebFlux


I'm looking for a simple example of error handling with WebFlux. I've read lots of stuff online, but can't find something that fits what I want.

I'm running with Spring Boot 2.45

I am calling services like this:

Mono<ResponseObject> mono = webClient.post()
   .uri(url.toString())
   .header("Authorization", authToken)
   .body(Mono.just(contract), contract.getClass())
   .retrieve()
   .bodyToMono(ResponseObject.class);

All of my services return Json that is deserialized to ResposeObject which looks something like this:

"success" : true,
"httpStatus": 200,
"messages" : [
   "Informational message or, if not 200, then error messages"
],
result: {
   "data": {}
}

data is simply a map of objects that are the result of the service call. If there is an error, obviously success is false.

When I eventually do a ResponseObject response = mono.block(), I want to get a ResponseObject each time, even if there was an error. My service returns a ResponseObject even if it returns an http status of 400, but WebFlux seems to intercept this and throws an exception. Obviously, there might also be 400 and 500 errors where the service wasn't even called. But I still want to wrap whatever message I get into a ResponseObject. How can I eliminate all exceptions and always get a ResponseObject returned?

Update Just want to clarify that the service itself is not a Reactive Webflux service. It is not returning a Mono. Instead, it is calling out to other Restful services, and I want to do that using Webflux. So what I do is I call the external service, and then this service does a block(). In most cases, I'm calling multiple services, and then I do a Mono.zip and call block() to wait for all of them.

This seems to be what I want to do: Spring Webflux : Webclient : Get body on error, but still can't get it working. Not sure what exchange() is


Solution

  • Correct way of handling this is via .onErrorResume that allows you to subscribe to a fallback publisher using a function, when any error occurs. You can look at the generated exception and return a custom fallback response.

    You can do something like this:

    Mono<ResponseObject> mono = webClient.post()
       .uri(url.toString())
       .header("Authorization", authToken)
       .bodyValue(contract)
       .exchangeToMono(response -> {
          if (response.statusCode().equals(HttpStatus.OK)) {
              return response.bodyToMono(ResponseObject.class);
          }
          else if (response.statusCode().is4xxClientError()) {
              return response.bodyToMono(ResponseObject.class);
          }
          else {
              Mono<WebClientResponseException> wcre = response.createException();
              // examine wcre and create custom ResponseObject
    
              ResponseObject customRO = new ResponseObject();
              customRO.setSuccess(false);
              customRO.setHttpStatus(response.rawStatusCode());
              // you can set more default properties in response here
              return Mono.just( customRO );
          }
       });
    

    Moreover, you should not be using .block() anywhere in your Java code. Just make sure to return a Mono<ResponseObject> from your REST controller. If you want to examine response before returning to client you can do so in a .map() hander like this at the end of pipeline (right after .onErrorResume handler)

       .map(response -> {
          // examine content of response
    
          // in the end just return it
          return response;
       });