Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catch exceptions with try/catch using Spring's WebClient with blocking/syncronous request

I need to make a syncronous, blocking request, and I'm using Spring's WebClient instead of Spring's RestTemplate due to the latter being deprecated. I don't need the reactive features in this case, I just want to consume a REST API in a straightforward way without including additional dependencies.

I have the following code, which works as intended:

MyObject object = webClient.get()
    .uri( myUri )
    .retrieve()
    .bodyToMono( MyObject.class )
    .block()

However, I need to manage the cases when I either can't connect to the API, or if I connect but I get a 4xx/5xx code.

So, the straightforward way would be to just put the call inside a try/catch, and catch Spring's WebClientResponseException, which is thrown by .bodyToMono if it gets a 4xx/5xx code:

import org.springframework.web.reactive.function.client.WebClientResponseException;

try {

    MyObject object = webClient.get()
        .uri( myUri )
        .retrieve()
        .bodyToMono( MyObject.class )
        .block()

}

catch ( WebClientResponseException e ) {

    // Logic to handle the exception.

}

This works fine, but doesn't work if the connection is refused (say, if the URL is wrong or if the service is down). In this case, I get the following in my console:

reactor.core.Exceptions$ErrorCallbackNotImplemented: io.netty.channel.AbstractChannel$AnnotatedConnectException: finishConnect(..) failed: Connection refused: /127.0.0.1:8090 Caused by: io.netty.channel.AbstractChannel$AnnotatedConnectException: finishConnect(..) failed: Connection refused: /127.0.0.1:8090 Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): |_ checkpoint ⇢ Request to GET http://127.0.0.1:8090/test [DefaultWebClient] Stack trace: Caused by: java.net.ConnectException: finishConnect(..) failed: Connection refused at io.netty.channel.unix.Errors.throwConnectException(Errors.java:124) ~[netty-transport-native-unix-common-4.1.48.Final.jar:4.1.48.Final] (...)

I'm not sure which exception I need to catch to handle this case.

Besides the above, I also would like to throw a custom exception if the connection is refused and a different custom exception if I get an error code. In the second case, I tried using the .onStatus method:

try {

    MyObject object = webClient.get()
        .uri( myUri )
        .retrieve()
        .onStatus( HttpStatus::is4xxClientError, response -> { 
            return Mono.error( new CustomClientException( "A client error ocurred" ) );
        })
        .bodyToMono( MyObject.class )
        .block()

}

catch ( CustomClientException e ) {

    // Logic to handle the exception.

}

But the exception is not caught inside the catch block, although the stack trace does appear on console.

Is there any way to handle 4xx/5xx codes and connection errors using a try/catch block, hopefully with custom exceptions? Or should I use a different web client and/or change my approach? I'm not familiar with reactive programming.

Thanks in advance.

like image 320
afs35 Avatar asked Jul 17 '20 21:07

afs35


People also ask

How does spring boot handle WebClient exceptions?

We can use onStatus(Predicate<HttpStatus> statusPredicate, Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction) method to handle or customize the exception. We recommend, go through the Spring Boot– Consuming a REST Services with WebClient article before proceeding.

What exception does WebClient throw?

As you can see, ServiceException includes both a message and a status code. That's the exception that gets included in the Mono publisher. It's also the exception that gets thrown when the WebClient encounters a 405 HTTP status code.

How does WebClient handle error response?

While Initialising WebClient 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.

Can we use try catch and throws together in Java?

Q #2) Can we use throws, try and catch in a single method? Answer: No. You cannot throw the exception and also catch it in the same method. The exception that is declared using throws is to be handled in the calling method that calls the method that has thrown the exception.


1 Answers

With retrieve(), all exceptions that occur on the underlying HTTP client are wrapped in a RuntimeException called ReactiveException. This is done as a way to bubble up the checked exception through the reactive interface.

A means is provided to get the actual wrapped exception in Exceptions.unwrap(). You can then throw the unwrapped exception, and it can be caught later where appropriate. One way to do this might be the following:

 void makeHttpCall(..) throws Exception {
    try {
        // ..
        // webclient request code
        // ..
    } catch(Exception e) {
        throw Exceptions.unwrap(e);
    }
 }

 // somewhere else:
 try {
     makeHttpCall(..);
 } catch (ConnectException e) {
     // Do your specific error handling
 }

I'm not really fond of declaring a method with throws Exception but at this point it's not at all known what it could be.

like image 80
rewolf Avatar answered Oct 16 '22 23:10

rewolf