Q:

How to handle joining multiple asynchronous CompletableFutures?

I've read that CompletableFuture has the ability to merge multiple futures with the runAfterBoth but what if I want to merge more than two?
CompletableFuture<Boolean> a = new CompletableFuture<>();
CompletableFuture<Boolean> b = new CompletableFuture<>();
CompletableFuture<Boolean> c = new CompletableFuture<>();

List<CompletableFuture<Boolean>> list = new LinkedList<>();

list.add(a);
list.add(b);
list.add(c);

// Could be any number
for (CompletableFuture<Boolean> f : list) {
   f.runAfter..
}

My use case is that I'm sending messages out to multiple sockets to locate a single object which may or may not be on any one of them.
I'm currently looking at this as a solution:
CompletableFuture<Boolean> a = new CompletableFuture<>();
CompletableFuture<Boolean> b = new CompletableFuture<>();
CompletableFuture<Boolean> c = new CompletableFuture<>();

List<CompletableFuture<Boolean>> list = new LinkedList<>();

list.add(a);
list.add(b);
list.add(c);

CompletableFuture<Boolean> result = new CompletableFuture<>();

Thread accept = new Thread(() -> {
   for (CompletableFuture<Boolean> f : list)
      if (f.join() != null)
         result.complete(f.join());
});

accept.start();

// Actual boolean value returned
result.get();

But it's kind of a mess. And in my case, I want to continue processing as soon as I get a valid result (not null) instead of waiting on the invalid results as well.
For example, a takes 5 seconds and the loop is waiting on it even though b has already completed in 2 seconds; but the loop doesn't know that because it's still waiting on a.
Is there a pattern to work with joining multiple asynchronous futures where I can respond immediately on a successful completion?
Another possibility:
public static class FutureUtil {
public static <T> CompletableFuture<T> anyOfNot(
   Collection<CompletableFuture<T>> collection,
   T value,
   T defaultValue)
{
   CompletableFuture<T> result = new CompletableFuture<>();

   new Thread(() -> {
      for (CompletableFuture<T> f : collection) {
         f.thenAccept((
            T r) -> {
            if ((r != null && !r.equals(value))
               || (value != null && !value.equals(r)))
               result.complete(r);
         });
      }

      try {
         for (CompletableFuture<T> f : collection)
            f.get();
      }
      catch (Exception ex) {
         result.completeExceptionally(ex);
      }

      result.complete(defaultValue);
   }).start();

   return result;
}
}

Example use:
CompletableFuture<Boolean> a = new CompletableFuture<>();
CompletableFuture<Boolean> b = new CompletableFuture<>();
CompletableFuture<Boolean> c = new CompletableFuture<>();

List<CompletableFuture<Boolean>> list = new LinkedList<>();

list.add(a);
list.add(b);
list.add(c);

CompletableFuture<Boolean> result = FutureUtil.anyOfNot(list, null, false);

result.get();

A:

If you know that at least one if the CFs in the List will complete with a non-null value, you can try this:
public static <T> CompletableFuture<T> firstNonNull(List<CompletableFuture<T>> completableFutures) {

    final CompletableFuture<T> completableFutureResult = new CompletableFuture<>();
    completableFutures.forEach(cf -> cf.thenAccept(v -> {
        if (v != null) {
            completableFutureResult.complete(v);
        }
    }));
    return completableFutureResult;
}

If there is no guarantee that at least one of the CFs will return a non-null value, you need something more complicated:
public static <T> CompletableFuture<T> firstNonNull(List<CompletableFuture<T>> completableFutures, T defaultValue) {

    final CompletableFuture<T> completableFutureResult = new CompletableFuture<>();
    completableFutures.forEach(cf -> cf.thenAccept(v -> {
        if (v != null) {
            completableFutureResult.complete(v);
        }
    }));
    //handling the situation where all the CFs returned null 
    CompletableFuture<Void> allCompleted = CompletableFuture
        .allOf((CompletableFuture<?>[]) completableFutures.toArray());
    allCompleted.thenRun(() -> {
        //checking first if any of the completed delivered a non-null value, to avoid race conditions with the block above 
        completableFutures.forEach(cf -> {
            final T result = cf.join();
            if (result != null) {
                completableFutureResult.complete(result);
            }
        });
        //if still not completed, completing with default value
        if ( !completableFutureResult.isDone()) {
            completableFutureResult.complete(defaultValue);
        }
    });
    return completableFutureResult;
}

