Question 18 · Section 19

How to cancel CompletableFuture execution

CompletableFuture can be cancelled using the cancel() method, but there are nuances:

Language versions: English Russian Ukrainian

🟢 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

  1. 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]]