In this article, we’ll revisit the CompletableFuture.applyToEither method and try to figure out a workaround for one of its issues.

## CompletableFuture.applyToEither and its Quirks

The CompletableFuture.applyToEither method is pretty self-explanatory. The idea is that you can declaratively provide a function that should be applied to a value of the first CompletableFuture that completed normally.

Imagine that we have two futures and want to print the value of whichever comes first:

```CompletableFuture<Integer> f1 = CompletableFuture.completedFuture(42);
CompletableFuture<Integer> f2 = new CompletableFuture<>();

f1.applyToEither(f2, i -> i).thenAccept(System.out::println).join();

// 42```

Naturally, if we swap f1 with f2, we expect to witness the same result:

```CompletableFuture<Integer> f1 = CompletableFuture.completedFuture(42);
CompletableFuture<Integer> f2 = new CompletableFuture<>();

f2.applyToEither(f1, i -> i).thenAccept(System.out::println).join();

// 42```

Which is indeed the case.

So, where’s the problem?

## Exception Handling

Unfortunately, the above breaks apart if we sprinkle some exceptions on them.

```CompletableFuture<Integer> f1 = CompletableFuture.completedFuture(42);
CompletableFuture<Integer> f2 = CompletableFuture.failedFuture(new NullPointerException("oh no, anyway"));

f1.applyToEither(f2, i -> i).thenAccept(System.out::println).join();

// 42```

So far, so good, but what happens if we swap f1 with f2 again?

```CompletableFuture<Integer> f1 = CompletableFuture.completedFuture(42);
CompletableFuture<Integer> f2 = CompletableFuture.failedFuture(new NullPointerException("oh no, anyway"));

f2.applyToEither(f1, i -> i).thenAccept(System.out::println).join();

// Exception in thread "main" java.util.concurrent.CompletionException: java.lang.NullPointerException: oh no, anyway```

It turns out that despite the fact that the other future is already completed, we never progress because the exception ends up propagating to the joint future which is not a behaviour many would expect.

Personally, I perceive it as a bug.

## Solution

In order to circumvent the issue, we need to craft a new method since CompletableFuture.anyOf behaves in a similar fashion and won’t be helpful here.

To do that, we need to simply create a new CompletableFuture and introduce a race between two completions:

```public static <T> CompletableFuture<T> either(
CompletableFuture<T> f1, CompletableFuture<T> f2) {
CompletableFuture<T> result = new CompletableFuture<>();
// ...

f1.thenAccept(result::complete);
f2.thenAccept(result::complete);
return result;
}```

However, it’s not enough. What if all futures completed exceptionally? We’d be stuck with an incomplete future forever.

This can be achieved by piggybacking onto CompletableFuture.allOf:

```CompletableFuture.allOf(f1, f2).whenComplete((__, throwable) -> {
if (f1.isCompletedExceptionally() && f2.isCompletedExceptionally()) {
result.completeExceptionally(throwable);
}
});```

And here is the complete sample:

```public static <T> CompletableFuture<T> either(
CompletableFuture<T> f1, CompletableFuture<T> f2) {
CompletableFuture<T> result = new CompletableFuture<>();
CompletableFuture.allOf(f1, f2).whenComplete((__, throwable) -> {
if (f1.isCompletedExceptionally() && f2.isCompletedExceptionally()) {
result.completeExceptionally(throwable);
}
});

f1.thenAccept(result::complete);
f2.thenAccept(result::complete);
return result;
}```

And in action:

```CompletableFuture<Integer> f1 = CompletableFuture.completedFuture(42);
CompletableFuture<Integer> f2 = CompletableFuture.failedFuture(new NullPointerException("oh no, anyway"));

either(f1, f2).thenAccept(System.out::println).join(); // 42
either(f2, f1).thenAccept(System.out::println).join(); // 42```

## Conclusion

CompletableFuture.applyToEither works in a manner that’s mostly unacceptable for production and if you are looking after similar behaviour, you might need to craft a suitable utility method yourself.

All code samples can be found on GitHub along with other CompletableFuture utilities.

If you enjoyed the content, consider supporting the site: