2

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

Peter Kronenberg
  • 878
  • 10
  • 32

1 Answers1

2

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;
   });
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • Thanks, let me play around with this. What do you mean when you say I shouldn't use block() anywhere in the Java code? I thought that is the way to essentially subscribe to the process so that it actually executes. How do I kick of the Webflux code and then get the response? – Peter Kronenberg Jul 13 '21 at 15:17
  • I think I understand what you mean about not using block(). I'm editing my original question to clarify what I'm doing here. Your solution works if I never reach the endpoint. For example, if there is a network error and I get a 500 response, this let's me wrap the exception in my ResponseObject. However, how do I handle it if my service *does* return a response object, but it returns a 400 because the parameters were wrong or something like that. I want to use the ResponseObject that was returned, not create a new one – Peter Kronenberg Jul 13 '21 at 15:47
  • 1
    whoops, I meant to edit my original question, and instead I edited a comment. Didn't mean to do that and not sure how to cancel it – Peter Kronenberg Jul 13 '21 at 15:51
  • This seems to be what I want to do: https://stackoverflow.com/questions/44593066/spring-webflux-webclient-get-body-on-error – Peter Kronenberg Jul 13 '21 at 16:06
  • 1
    Not sure how that answer helps you as you want to grab response from client in case of error. I will make a further update for that. – anubhava Jul 13 '21 at 16:44
  • Yes, exactly. I want to get the response from the client even on an error – Peter Kronenberg Jul 13 '21 at 17:03
  • please try my updated answer and see what you get in `response body:` log message for `400` status. Once confirmed I will make further modification. – anubhava Jul 13 '21 at 17:39
  • Thanks, this is definitely getting close. I've been playing around with exchangeToMono() as well, which sort of works. It avoids the WebClientResponseException and just uses the body from the service. The problem I'm having is that when I get an exception other than WCRE, when constructing my dummy ResponseObject, I can't figure out how to get the Httpstatus code. All I get passed to me is the Exception. – Peter Kronenberg Jul 13 '21 at 18:11
  • `WebClientResponseException` is guaranteed to be there if it is a client exception from remote service. It won't be there when exception is raised from your end like Socket exception or read timeout etc. – anubhava Jul 13 '21 at 18:14
  • Ok, that might help. So if I use exchangeToMono(), that would eliminate all occurrences of `WebClientResponseException`, correct? So I can assume that any other exception I get, where I have to build the ResponseObject myself, is a fatal exception from the client side – Peter Kronenberg Jul 13 '21 at 18:19
  • Yes using `exchangeToMono()` is good idea for advanced response processing. Please check updated answer . – anubhava Jul 13 '21 at 18:56