CompletableFuture Since Java 9: orTimeout or completeOnTimeout When a Deadline Fires?
0 reputation · 30 Mar 2025, 05:28 UTC
0 reputation · 30 Mar 2025, 05:28 UTC
Since Java 9, CompletableFuture includes two built-in deadline methods: orTimeout, which completes the future exceptionally with a TimeoutException, and completeOnTimeout, which completes it normally with a fallback value. Both only change the future's completion state.
The design tension is that neither method interrupts the task already submitted to the executor. When a deadline fires, dependent stages settle, but the underlying work keeps running — on ForkJoinPool.commonPool() for supplyAsync without an explicit executor, or on whatever custom ExecutorService was passed in.
Assuming a current LTS baseline such as JDK 17, the open decision is which strategy fits a pipeline whose callers expect graceful degradation:
TimeoutException so retry and metrics logic can react, or is a silent default acceptable when downstream code cannot distinguish a fallback from a real result?orTimeout fires — or is it simply left to finish?29775 reputation · 30 Mar 2025, 11:25 UTC
Both orTimeout and completeOnTimeout only change the completion state of the original CompletableFuture. They do not interrupt or cancel the asynchronous task that produced the value. The behavior is identical regardless of whether the task used the common pool or a custom ExecutorService, except for the thread that runs the timeout logic.
orTimeout completes the future exceptionally with java.util.concurrent.TimeoutException. This is useful when callers need to distinguish a timeout from a real result – e.g., to retry, to record a metric, or to propagate the error further.completeOnTimeout completes the future normally with the supplied fallback value. Downstream stages see a “normal” result and cannot tell whether it came from the original task or the timeout. Use this when graceful degradation is acceptable and callers treat the fallback as just another valid value.Because the timeout does not cancel the underlying task, the work continues to run on whatever executor it was submitted to. If the task holds resources or is long‑running, you must handle cancellation yourself:
java.util.concurrent.Future that you keep a reference to (e.g., by submitting a Callable to a ThreadPoolExecutor and storing the returned Future). After orTimeout fires, call Future.cancel(true) to interrupt the thread.CompletableFuture.cancel(boolean) before the timeout fires. If the task checks Thread.currentThread().isInterrupted() or throws InterruptedException, it will terminate promptly.ExecutorService that supports graceful shutdown.The timeout itself is scheduled by CompletableFuture.delayedExecutor, which by default uses ForkJoinPool.commonPool(). The original task runs on the executor you passed to supplyAsync (or the common pool if none). The semantics of completion state change are the same regardless of the executor. If you want the timeout logic to run on the same executor as the task, supply that executor to delayedExecutor:
CompletableFuture<T> cf = CompletableFuture.supplyAsync(() -> doWork(), customExecutor)
.orTimeout(1, TimeUnit.SECONDS, customExecutor); // timeout on customExecutor
Note that the timeout executor is independent; if you omit it, the common pool may compete for threads with a bounded or single‑threaded executor, potentially delaying the timeout.
ExecutorService pool = Executors.newSingleThreadExecutor();
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
System.out.println("Task thread: " + Thread.currentThread());
try { Thread.sleep(5000); } catch (InterruptedException e) { System.out.println("Interrupted"); }
return "ok";
}, pool).orTimeout(1, TimeUnit.SECONDS);
cf.whenComplete((res, ex) -> {
System.out.println("Completion: " + (ex == null ? res : ex));
});
pool.shutdown();
Running this prints that the future fails after 1 s, while the task thread continues sleeping until the pool shuts down.
Use orTimeout when callers need to react to a timeout as an error; use completeOnTimeout for silent degradation. Neither method cancels the underlying work; you must manage cancellation explicitly if required. The completion semantics are identical for custom executors, but the timeout’s scheduling thread defaults to the common pool unless overridden.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.