I'm receiving responses from a service call via CompletableFuture. I'd like to handle some known exceptions the service returns — such as optimistic concurrency control conflicts.
Here's what I've got. Is there a better way to do this which does not wrap the exceptions or use SneakyThrows? Wrapping exceptions would mean other exception handlers must inspect causal chains instead of merely using instanceof
.
someService.call(request)
.handle((response, error) -> {
if (error == null)
return CompletableFuture.completedFuture(response);
else if (error instanceof OCCException)
return CompletableFuture.completedFuture(makeDefaultResponse());
CompletableFuture<Response> errorFuture = new CompletableFuture<>();
errorFuture.completeExceptionally(error);
return errorFuture;
}).thenCompose(Function.identity());
Along the same vein, is there a way to replicated guava's withFallback without the wrap-unwrap?
CompletableFuture<T> withFallback(CompletableFuture<T> future,
Function<Throwable, ? extends CompletableFuture<T>> fallback) {
return future.handle((response, error) -> {
if (error == null)
return CompletableFuture.completedFuture(response);
else
return fallback.apply(error);
}).thenCompose(Function.identity());
}
...
// Here's the first part of the question implemented using withFallback.
// It's a little cleaner, but it still requires wrapping as a future.
withFallback(someService.call(request), error -> {
if (error instanceof OCCException)
return CompletableFuture.completedFuture(makeDefaultResponse());
CompletableFuture<Response> errorFuture = new CompletableFuture<>();
errorFuture.completeExceptionally(error);
return errorFuture;
});
For completeness, here is what it would look like if I allowed the exceptions to be wrapped. (I've got a unit test to verify the thrown exception propagates down the chain):
someService.call(request)
.exceptionally(error -> {
if (error instanceof OCCException)
return makeDefaultResponse();
else
// wrap because error is declared as a checked exception
throw new RuntimeException(error);
});