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?