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.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With