I'm used to the ListenableFuture
pattern, with onSuccess()
and onFailure()
callbacks, e.g.
ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
ListenableFuture<String> future = service.submit(...)
Futures.addCallback(future, new FutureCallback<String>() {
public void onSuccess(String result) {
handleResult(result);
}
public void onFailure(Throwable t) {
log.error("Unexpected error", t);
}
})
It seems like Java 8's CompletableFuture
is meant to handle more or less the same use case. Naively, I could start to translate the above example as:
CompletableFuture<String> future = CompletableFuture<String>.supplyAsync(...)
.thenAccept(this::handleResult)
.exceptionally((t) -> log.error("Unexpected error", t));
This is certainly less verbose than the ListenableFuture
version and looks very promising.
However, it doesn't compile, because exceptionally()
doesn't take a Consumer<Throwable>
, it takes a Function<Throwable, ? extends T>
-- in this case, a Function<Throwable, ? extends String>
.
This means that I can't just log the error, I have to come up with a String
value to return in the error case, and there is no meaningful String
value to return in the error case. I can return null
, just to get the code to compile:
.exceptionally((t) -> {
log.error("Unexpected error", t);
return null; // hope this is ignored
});
But this is starting to get verbose again, and beyond verbosity, I don't like having that null
floating around -- it suggests that someone might try to retrieve or capture that value, and that at some point much later I might have an unexpected NullPointerException
.
If exceptionally()
took a Function<Throwable, Supplier<T>>
I could at least do something like this --
.exceptionally((t) -> {
log.error("Unexpected error", t);
return () -> {
throw new IllegalStateException("why are you invoking this?");
}
});
-- but it doesn't.
What's the right thing to do when exceptionally()
should never produce a valid value? Is there something else I can do with CompletableFuture
, or something else in the new Java 8 libraries, that better supports this use case?
A correct corresponding transformation with CompletableFuture
is:
CompletableFuture<String> future = CompletableFuture.supplyAsync(...);
future.thenAccept(this::handleResult);
future.exceptionally(t -> {
log.error("Unexpected error", t);
return null;
});
Another way:
CompletableFuture<String> future = CompletableFuture.supplyAsync(...);
future
.whenComplete((r, t) -> {
if (t != null) {
log.error("Unexpected error", t);
}
else {
this.handleResult(r);
}
});
The interesting part here is that you were chaining futures in your examples. The seemingly fluent syntax is actually chaining futures, but it seems you don't want that here.
The future returned by whenComplete
might be interesting if you want to return a future that processes something with an internal future's outcome. It preserves the current future's exception, if any. However, if the future completed normally and the continuation throws, it'll complete exceptionally with the thrown exception.
The difference is that anything that happens after future
completes will happen before the next continuation. Using exceptionally
and thenAccept
is equivalent if you're the future
's end-user, but if you're providing a future back to a caller, either one will process without a completion notification (as if in the background, if you may), most probably the exceptionally
continuation since you'll probably want the exception to cascade on further continuations.