As far as I love Java 8’s CompletableFuture, it has its downsides – idiomatic handling of timeouts is one of them.
Luckily, JDK 9 brought two new methods that provide the functionality everyone longed for – which is crucial for ensuring the proper resiliency when working with asynchronous processing.
In this super-short article, I will try to raise the awareness of the new crucial API methods.
CompletableFuture#orTimeout
Simply put, after calling the above method. The future will throw an ExecutionException if it doesn’t complete within a specified timeout.
A simple example:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(this::computeEndlessly) .orTimeout(1, TimeUnit.SECONDS); future.get(); // java.util.concurrent.ExecutionException after waiting for 1 second
CompletableFuture#completeOnTimeout
In this option, we can return a default value once the timeout is reached:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(this::computeEndlessly) .completeOnTimeout(42, 1, TimeUnit.SECONDS); Integer result = future.get(); // 42
As simple as that, although I don’t necessarily like the fact that we’re always forced to precompute the default value. If you look at the signature, you will see, that the value isn’t computed lazily:
public CompletableFuture<T> completeOnTimeout(T value, long timeout, TimeUnit unit)
This could’ve been easily achieved, for example, by providing an alternative API method that accepted Supplier<T> instead of plain T – more about the idea can be found here.
Keep in mind that CompletableFutures are not bound to threads executing these jobs, so if even though CompletableFuture timeouts, the underlying task is still running in the background.
Sources
The complete example can be found on GitHub.