Reputation: 5835
I'm working on a project in which we are migrating from RestTemplate
to WebClient
.
The WebClient
is implemented like this:
try {
...
return webClient
.get()
.uri(someUri)
.retrieve()
.toEntity(SomeBusinessClass.class).block()
} catch(WebClientException e) {
// do some stuff
// want to catch IOExceptions here as well
}
While refactoring the code I had to refactor the tests as well and I've come across a test in which we basically throw an ConnectException
to see if our internal code catch them according to our needs. With RestTemplate
's exception classes we was able to define the exception like this:
ResourceAccessException exc = new ResourceAccessException("I/O error on GET request", new ConnectException("Connection refused: connect"))
I tried to do the same with WebClient
's provided exception class WebClientException
but that's an abstract class and the only class inheriting from it is WebClientResponseException
and that don't provide a constructor which would allow to do the same. So my only option was to do it with RuntimeException
:
RuntimeException exc = new RuntimeException("I/O error on GET request", new ConnectException("Connection refused: connect"))
But since I don't want to rewrite our internal code to catch exceptions on RuntimeException
level but on WebClientException
level, is that not an option and I'm wondering how to do that?
I tried to find out in the Spring docs how to handle IOException
's while using WebClient
but couldn't find anything.
What would be the approach here?
Upvotes: 4
Views: 4070
Reputation: 72284
The nicest way would almost certainly be to handle all errors in the reactive stream itself. Server response errors are usually best handled by using exchange()
rather than retrieve()
and then dealing with the response manually, and an underlying IOException
by using the onErrorResume()
, onErrorReturn()
etc. reactive operators available for this purpose.
However, you mention you're migrating from blocking code, so I understand that practically that may not (yet) be on the cards. If you want to stick to catching exceptions:
But since I don't want to rewrite our internal code to catch exceptions on RuntimeException level but on WebClientException level, is that not an option and I'm wondering how to do that?
Wanting to catch all transport errors under the umbrella of WebClientException
is not a sensible option. As you say, neither is just catching RuntimeException
for obvious reasons.
Simplifying it, WebClientException
means "I connected to the URL and sent stuff to it without an issue, but it told me to sod off" (ie. it generated an error code rather than a 200 response.)
That might be because of a 404 (resource not found), 500 (server error), 418 (you're trying to connect to a teapot, not a server), etc.
IOException
on the other hand means "Couldn't even establish a connection to this URL." That could be because the connection was actively refused, the domain name couldn't be resolved, the SSL cert expired, etc.
The two are not analogous, and it would be rather odd and confusing to treat them that way.
If you want to handle them in the same block, then that's fine - naively you might just do:
catch(WebClientException|IOException e) {
// do some stuff
}
...but you can't of course, because IOException
is checked. (Reactive streams in Java don't throw checked exceptions, each checked exception is mapped to a RuntimeException
instead.)
However, you can map all IOException
to an UncheckedIOException
:
return webClient
.get()
.uri(someUri)
.retrieve()
.toEntity(SomeBusinessClass.class)
.onErrorMap(IOException.class, UncheckedIOException::new)
.block()
...and then either do catch(WebClientException|UncheckedIOException ex)
, or deal with them in separate catch blocks.
This certainly isn't the "nice" way to handle exceptions from a reactive mindset, but if you're aiming to migrate with the fewest possible changes, this is likely what you're after.
Upvotes: 8