Constantin Beer
Constantin Beer

Reputation: 5835

How to catch IOException's while using Spring WebClient?

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

Answers (1)

Michael Berry
Michael Berry

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

Related Questions