Issue
When WebClient calls an external api (with different query params) asynchronously with a wrong token, the first few returns with 401 UNAUTHORIZED and the control flow stops with Exceptions.CompositeException
. We've a filter like below added to the WebClient
instance that checks for 4xx and 5xx response status code to throw a custom exception
private final ExchangeFilterFunction responseFilter() {
return ExchangeFilterFunction
.ofResponseProcessor(response -> response.statusCode().isError() ? error(() -> new CustomException("API Error")) : just(response));
private WebClient buildWebClient(WebClient.Builder builder) {
return builder
.filter(responseFilter())
.build();
}
Now the issue is CustomException
doesn't get thrown when the first call returns with an error (401) but rather aggregates a bunch of calls and throws Exceptions.CompositeException
. In the logs we can see atleast 3 or 4 401 UNAUTHORIZED
before Composite failure. It doesn't throw the expected CustomException
when the first one fails. This WebClient call happens inside a Flux.zip
and I'm not sure whether that has something to do with the issue.
Solution
When using any of the DelayError
variant operators, reactor will collect any exceptions (if there this more than 1) into a CompositeException
.
Reactor provide a utility method Exceptions.unwrapMultiple
to help unpack a composite exception into a list of throwables if you want to only propagate a subset or single error upstream - java docs
Exceptions.unwrapMultiple
Attempt to unwrap a Throwable into a List of Throwables. This is only done on the condition that said Throwable is a composite exception built by multiple(Throwable...), in which case the list contains the exceptions wrapped as suppressed exceptions in the composite. In any other case, the list only contains the input Throwable (or is empty in case of null input).
Example
Below is a small example using unwrapMultiple
to propagate only the first Throwable
within the CompositeException
.
Flux<Object> range = Flux.range(1, 10)
.flatMapDelayError(i -> Mono.error(new IllegalArgumentException(String.format("Error: %s", i))), 10, 1)
.onErrorResume(throwable -> Mono.error(Exceptions.unwrapMultiple(throwable).get(0)));
StepVerifier.create(range)
.expectError(IllegalArgumentException.class)
.verify();
Answered By - Michael McFadyen
Answer Checked By - Candace Johnson (JavaFixing Volunteer)