afs35
afs35

Reputation: 379

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.

Upvotes: 12

Views: 16414

Answers (1)

rewolf
rewolf

Reputation: 5871

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.

Upvotes: 7

Related Questions