Reputation: 379
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
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