We need to add blocking methods to CompletionStage!

Martin Buchholz martinrb at google.com
Wed Sep 21 20:43:13 UTC 2016

(Sorry to re-open this discussion)

The separation of a read-only CompletionStage from CompletableFuture is
great.  I'm a fan of the scala style Promise/Future split as described in
http://docs.scala-lang.org/overviews/core/futures.html, but: we need to
re-add (safe, read-only) blocking methods like join.  Java is not Node.js,
where there are no threads but there is a universal event loop.  Java
programmers are used to Future, where the *only* way to use a future's
value is to block waiting for it.  The existing CompletionStage methods are
a better scaling alternative to blocking all the time, but blocking is
almost always eventually necessary in Java.  For example, junit test
methods that start any asynchronous computation need to block until the
computation is done, before returning.

As Viktor has pointed out, users can always implement blocking themselves
by writing

    static <T> CompletableFuture<T> toCompletableFuture(CompletionStage<T>
stage) {
        CompletableFuture<T> f = new CompletableFuture<>();
        stage.handle((T t, Throwable ex) -> {
                         if (ex != null) f.completeExceptionally(ex);
                         else f.complete(t);
                         return null;
        return f;

    static <T> T join(CompletionStage<T> stage) {
        return toCompletableFuture(stage).join();

but unlike Viktor, I think it's unreasonable to not provide this for users
(especially when we can do so more efficiently).  What is happening instead
is API providers not using CompletionStage as return values in public APIs
because of the lack of convenient blocking, and instead returning
CompletableFuture, which is a tragic software engineering failure.

Re-adding join is easy.  We discourage CompletionStage.toCompletableFuture
from throwing UnsupportedOperationException, and implement join as:

    public default T join() { return toCompletableFuture().join(); }

There is a risk of multiple-inheritance conflict with Future if we add e.g.
isDone(), but there are no current plans to turn those Future methods into
default methods, and even if we did in some future release, it would be
only a source, not binary incompatibility, so far less serious.

More information about the core-libs-dev mailing list