-2

I'm trying to retrieve data from a REST service using Spring's WebClient and trying to throw the 4XX and 5XX in a Error instead of instead of a RuntimeException.

return webclient. 
    .get()
    .uri(myURI)
    .accept(MediaType.APPLICATION_JSON)
    .retrieve()
    .onStatus(HttpStatus::isError, handleError()
    .bodyToMono(String.class)
    .block();
private static Function<ClientResponse, Mono<? extends Throwable>> handleError() {
    return response -> response.bodyToMono(String.class).map(CustomError::new);
}

I'm trying to get this Error in a test but received a ReactiveException instead.

org.opentest4j.AssertionFailedError: Unexpected exception type thrown, 
Expected :class com.example.CustomError
Actual   :class reactor.core.Exceptions$ReactiveException

When I switch the extesion of CustomError from Error to RuntimeException the test pass.

Since the onStatus expcept a Function<ClientResponse, Mono<? extends Throwable>> I expected to be able to throw an Error in this call.

I'm working on a test library and a initial request is strictly necessary and without a successfully request the test must fail. A Error will be prevent anyone to catch any problem related with the REST request in their own tests.

  • As `Error` is a `Throwable` you can still catch them so your logic/reasoning is flawed you cannot catch them, because you can. Generally `Errors` are for unrecoverable situations. That being said the Error will never propagate to your client as that only knows about HTTP status codes. – M. Deinum Aug 02 '23 at 18:12
  • Yes, I'll follow the suggestion to use RuntimeException. I'll try to document better as possible to prevent anyone to catch exceptions from this library (An exception error should always fail the tests, never test in any scenario). – Rafael Zucareli Aug 03 '23 at 08:39

1 Answers1

0

You might want to try to return a Mono.error(new CustomError(response)); in your handleError() method. This way you've let the reactive pipeline know an error has occured and what to throw afterwards. Right now you just map to an Error object, which is not handled the same way as RuntimeExceptions.

I would also say that it is probably better to stick to Exceptions instead of Errors. Errors tend to be unrecoverable situations for the program to run (OutOfMemoryError for example), whereas Exceptions are just exceptional cases that could be reacted to potentially (retrying for a 503 status code, or reauthenticating when you receive a 401 status code back).

Example implementation

    public Mono<? extends Throwable> handleError(ClientResponse clientResponse) {
        String message = String.format("Service error: %s",
                clientResponse.statusCode().getReasonPhrase());
        return Mono.error(new CustomError(message));
    }

This is also a good answer: Correct way of throwing exceptions with Reactor

  • The solution solved my problem but I thought the logic should work better in a functional code than in a reactive one. I will use the suggestions to go back to a RuntimeException and document well to anyone who keeps looking when working with the library in a test for exception scenarios. – Rafael Zucareli Aug 03 '23 at 08:30