CompletableFuture.orTimeout completes that future exceptionally at its deadline, but it does not by itself stop work launched to complete it.
Java CompletableFuture timeout: completion is not worker cancellation
Separate the caller deadline from the task
A timed-out caller can stop waiting while the producer still holds a socket, thread, or database connection. The program coordinates with latches so the worker has definitely started before the future times out. After the timeout, it releases the worker and observes that the worker still completes its own work; its later complete call cannot replace the future's timeout result.
The timeout mutates the same CompletableFuture instance and is available from Java 9. A timeout on get(timeout, unit) instead limits only that wait and does not complete the future. Choose the two contracts deliberately. Interruption and cancellation requires cooperation from the task and its blocking APIs.
Bound resources independently
An application needs a deadline at the I/O call, a queue admission limit, and a policy for interrupting or abandoning late work. A future timeout is a result-state rule. It is not proof that the underlying operation stopped, nor that a remote side effect did not happen. If a retry follows, give it an idempotency key rather than assuming the first call vanished.
The explicit worker thread keeps this fixture small. In a service, use a named, owned executor with bounded admission and shutdown; executor saturation covers that layer.
Working program
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
public class LateRateQuote {
public static void main(String[] args) throws Exception {
CountDownLatch started = new CountDownLatch(1);
CountDownLatch release = new CountDownLatch(1);
AtomicBoolean workFinished = new AtomicBoolean();
CompletableFuture<String> quote = new CompletableFuture<>();
Thread worker = new Thread(() -> {
started.countDown();
try {
release.await();
workFinished.set(true);
quote.complete("rate-47");
} catch (InterruptedException stopped) {
Thread.currentThread().interrupt();
quote.completeExceptionally(stopped);
}
}, "rate-quote-worker");
worker.start();
if (!started.await(2, TimeUnit.SECONDS)) throw new IllegalStateException("worker did not start");
quote.orTimeout(50, TimeUnit.MILLISECONDS);
try { quote.get(2, TimeUnit.SECONDS); }
catch (ExecutionException failure) {
System.out.println(failure.getCause() instanceof TimeoutException);
} finally {
release.countDown();
worker.join(2_000);
}
System.out.println(workFinished.get());
System.out.println(quote.isCompletedExceptionally());
}
}Output
true
true
trueCost and ownership
The timeout scheduler holds a deadline until completion, while the worker retains its own resources until it exits. The sample caps every wait, but a real I/O operation must also have its own timeout or cancellation path. One timed-out future can otherwise leave unbounded late work behind under load.
Common Mistakes
- Do not assume orTimeout interrupts an already running worker.
- Do not retry a timed-out side effect without an idempotency policy.
- Do not confuse get(timeout) with a future that has completed exceptionally.
Read next
Java CompletableFuture: composition, failures and executor ownership, Java cancellation: timed waits and cooperative interruption, Java bounded executors: test saturation and rejected work, Java HTTP retry project: bounded attempts and one wait budget.
Continue with: Java CompletableFuture cancellation: distinguish result status from worker interruption, Java thenCompose: flatten a dependent asynchronous request.
