8

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);
    });

2 Answers 2

11

The guava style function you asked for could be implemented like this:

static <T> CompletableFuture<T> withFallback(CompletableFuture<T> future,
                  Function<Throwable, ? extends CompletableFuture<T>> fallback) {
  return future.handle((response, error) -> error)
    .thenCompose(error -> error!=null? fallback.apply(error): future);
}

being both, more compact and saving resources by reusing the source future for the case we don’t want to do any transformations. But to give the caller the opportunity to do the same without the introduction of another helper method, it would be very useful to change the method and use a BiFunction which gets the source future as additional parameter:

static <T> CompletableFuture<T> withFallback(CompletableFuture<T> future,
  BiFunction<CompletableFuture<T>, Throwable, ? extends CompletableFuture<T>>
                                                                      fallback) {
    return future.handle((response, error) -> error)
      .thenCompose(error -> error!=null? fallback.apply(future,error): future);
}

then you can use it like this:

withFallback(someService.call(request), (f,t) -> t instanceof OCCException?
                  CompletableFuture.completedFuture(makeDefaultResponse()): f)
2
  • That is beautiful! Moving the error through the future is a good idea. Commented Aug 18, 2014 at 18:09
  • 1
    Awesome! This saved my day. Commented Sep 14, 2017 at 14:16
5

The only way I can think of is to define a helper method like this:

static <T, E extends Throwable> CompletableFuture<T> betterHandle(CompletableFuture<T> source, Class<E> excClass, Supplier<T> sup) {
    CompletableFuture<T> result = new CompletableFuture<>();
    source.whenComplete( (t, ex) -> {
        if (ex == null) {
            result.complete(t);
        } else if (excClass.isInstance(ex)) {
            result.complete(sup.get());
        } else {
            result.completeExceptionally(ex);
        }
    });
    return result;
}

It's not pretty, but it will allow you to avoid wrapping exceptions:

CompletableFuture<...> result = betterHandle(
    someService.call(request), 
    OCCException.class, 
    () -> makeDefaultResponse()
);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.