How to cancel CompletableFuture execution
CompletableFuture can be cancelled using the cancel() method, but there are nuances:
🟢 Junior Level
CompletableFuture can be cancelled using the cancel() method, but there are nuances:
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
Thread.sleep(5000);
return "Done";
});
// Cancel
boolean cancelled = cf.cancel(true); // true = mayInterruptIfRunning
System.out.println(cf.isCancelled()); // true
Important: cancel() doesn’t always stop execution! It only sets the cancelled status.
🟡 Middle Level
How cancel works
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
// This task continues executing even after cancel!
Thread.sleep(5000);
return "Done";
});
cf.cancel(false); // does not interrupt
// The task is still running in the background!
Why: cancel() sets the result to CancellationException, but does not interrupt the thread.
Proper cancellation
1. Cooperative cancellation:
AtomicBoolean cancelled = new AtomicBoolean(false);
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
for (int i = 0; i < 1000; i++) {
if (cancelled.get()) {
throw new CancellationException("Task cancelled");
}
// do work
}
return "Done";
});
// Cancel
cancelled.set(true);
cf.cancel(false);
2. Timeout:
cf.orTimeout(5, TimeUnit.SECONDS); // auto-cancellation after 5 seconds
Typical mistakes
- Expecting interruption: ```java // ❌ cancel() does not interrupt the thread cf.cancel(true); // task continues executing
// ✅ Cooperative cancellation if (Thread.currentThread().isInterrupted()) { throw new CancellationException(); }
---
## 🔴 Senior Level
### Internal Implementation
```java
// Unlike FutureTask, CompletableFuture.cancel(true) attempts to
// interrupt the thread via Thread.interrupt(). But this only works
// if the task checks Thread.interrupted() or performs a blocking
// operation that responds to interrupt.
// In practice, cooperative cancellation (via volatile flag) is the primary approach.
Architectural Trade-offs
| Approach | Pros | Cons |
|---|---|---|
| cancel() | Simple | Does not interrupt |
| Cooperative | Reliable | Requires extra code |
| Timeout | Automatic | Time-based only |
Edge Cases
1. Dependent CFs:
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> cf2 = cf1.thenApply(s -> s + " World");
cf1.cancel(false);
// cf2 will also be cancelled — it depends on cf1
2. Interrupt flag:
// Result depends on timing. If the task has already completed — cancel()
// returns false and isCancelled() will be false. If the task is still running
// and responds to interrupt — it will be true. Thread.sleep() responds to interrupt.
// Thread.interrupted() can help
CompletableFuture.supplyAsync(() -> {
while (!Thread.currentThread().isInterrupted()) {
// work
}
throw new CancellationException();
});
Production Experience
public class CancellableTask {
private volatile boolean cancelled = false;
private CompletableFuture<String> future;
public CompletableFuture<String> start() {
future = CompletableFuture.supplyAsync(() -> {
while (!cancelled) {
// work
}
if (cancelled) {
throw new CancellationException();
}
return "Done";
});
return future;
}
public void cancel() {
cancelled = true;
if (future != null) {
future.cancel(false);
}
}
}
Best Practices
// ✅ Cooperative cancellation
AtomicBoolean cancelled = new AtomicBoolean(false);
// ✅ Timeout
cf.orTimeout(5, TimeUnit.SECONDS);
// ✅ Check interrupt flag
if (Thread.currentThread().isInterrupted()) throw new CancellationException();
// ❌ Expecting cancel() to interrupt the thread
// ❌ Ignoring CancellationException
🎯 Interview Cheat Sheet
Must know:
- cancel(true) attempts to interrupt the thread via Thread.interrupt(), but in practice doesn’t work reliably
- Cooperative cancellation (volatile flag / AtomicBoolean) is the primary approach
- orTimeout() — automatic time-based cancellation
- When root CF is cancelled, all dependents complete with CancellationException
- Cancelling a child chain does NOT affect the root CF
Common follow-up questions:
- Does cancel(true) interrupt execution? — No, it only sets the status. The task continues working
- How to cancel properly? — Cooperative: AtomicBoolean flag + check in loop + cancel(false)
- What happens to dependent CFs on cancellation? — They complete with CancellationException
- obtrudeValue for cancellation? — No, obtrudeValue forcibly changes the result — dangerous, causes race conditions
Red flags (DO NOT say):
- “cancel(true) guaranteed to stop the task” — only sets interrupt flag, the task may ignore it
- “After cancel() the task finishes immediately” — it continues executing, need cooperative cancellation
- “obtrudeValue is a normal way to cancel” — violates the single-completion contract, causes race conditions
Related topics:
- [[21. How to implement timeout for CompletableFuture]]
- [[27. Can you manually complete a CompletableFuture with a result]]
- [[20. Can you reuse a single CompletableFuture in multiple chains]]
- [[19. What happens if an exception occurs in CompletableFuture chain]]