skip to content

Completion Control & Inspection

Completing a future by hand (complete, completeExceptionally, orTimeout, completeOnTimeout) and inspecting it without blocking (getNow, isDone, isCompletedExceptionally). Manual completion is how you bridge callback-style APIs into a CompletableFuture chain.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

How do you manually drive a CompletableFuture to completion, and what is the difference between complete(value) and completeExceptionally(throwable)?

level: juniorimportance: must knowfreq 70%

answer

  1. complete = success, completeExceptionally = failure
  2. returns true only if YOU settled it (one winner)
  3. new CompletableFuture<>() starts incomplete
  4. bridges callback APIs into the CF world
  5. failure shows downstream as CompletionException(cause)

basics

~20 s

A CompletableFuture isn't always finished automatically — you can finish it yourself. complete(value) makes it succeed with that value; completeExceptionally(ex) makes it fail with that exception. Both return true only if your call was the one that completed it.

solid answer

~40 s

A CompletableFuture is a holder for a result that may arrive later. When you create one with `new CompletableFuture<>()` it starts incomplete, and code can manually settle it. `complete(value)` settles it successfully so downstream stages run with that value; `completeExceptionally(throwable)` settles it as a failure so downstream stages see that exception (wrapped in a CompletionException by join/then-stages). Each returns a boolean: true if this call transitioned the future from incomplete to complete, false if it was already settled — so only the first winner takes effect, later calls are ignored silently. This is the bridge pattern for adapting callback APIs: hand the future to a caller, then call complete/completeExceptionally from your callback when the async work finishes.

go deeper

for a junior

Knows complete sets a success value and completeExceptionally sets a failure, and that you create an incomplete future with the constructor.

for a middle

Explains the boolean return (first caller wins, later calls no-op) and uses the pattern to adapt a callback API into a CompletableFuture.

for a senior

Articulates exception wrapping (CompletionException/ExecutionException), how failure propagates past mapping stages, and the race-resolution semantics for competing completers.

for a principal

Designs robust adapter boundaries around this: idempotent completion under concurrent settlers, leak-free futures that always get settled (success, error, or timeout path), and reasons about it as a single-assignment cell.

### What a CompletableFuture is A `CompletableFuture<T>` is a container that will eventually hold either a **value** of type `T` or a **failure** (a `Throwable`). At any moment it is in one of two states: **incomplete** (no result yet) or **completed** (settled, either successfully or exceptionally). "Settled" is permanent — once it holds a result, that result never changes through normal means. Unlike a plain `Future`, a `CompletableFuture` can be **completed from the outside** — that's the "Completable" in the name. You don't have to wait for some executor to fill it; your own code can push a result into it. ### Creating an incomplete one ```java CompletableFuture<String> cf = new CompletableFuture<>(); ``` Right now `cf` has no value. Anyone who calls `cf.join()` will block forever until something settles it. ### complete(value) `boolean complete(T value)` tries to settle the future **successfully** with `value`. - If the future was incomplete, it becomes completed-successfully, every dependent stage (`thenApply`, `thenAccept`, …) that was waiting fires, and the method returns `true`. - If the future was **already** completed (by anyone, any way), nothing changes and it returns `false`. ### completeExceptionally(throwable) `boolean completeExceptionally(Throwable ex)` tries to settle the future as a **failure**. - If incomplete: it becomes completed-exceptionally with `ex`. Dependent stages that handle errors (`exceptionally`, `handle`, `whenComplete`) see it; plain mapping stages (`thenApply`) are skipped and the failure propagates. Returns `true`. - If already completed: no change, returns `false`. ### Why the boolean return matters Completion is a **race that exactly one caller wins**. Imagine two threads — one calls `complete("ok")`, another calls `completeExceptionally(new IOException())`. Exactly one of them returns `true` (the winner that flipped the state); the other returns `false` and is ignored. This makes the future a safe one-shot signal: you can have a success path and a timeout/cancel path racing, and whichever fires first wins deterministically. ### How failures look downstream When you read a failed future, the exception is usually **wrapped**. `join()` and the `thenX` chain wrap your `Throwable` in a `CompletionException` (and `get()` wraps it in an `ExecutionException`). So if you `completeExceptionally(new IllegalStateException("boom"))`, a caller doing `cf.join()` catches a `CompletionException` whose `getCause()` is your `IllegalStateException`. ### The classic use case: adapting a callback API ```java CompletableFuture<Response> call(Request r) { CompletableFuture<Response> cf = new CompletableFuture<>(); httpClient.sendAsync(r, new Callback() { public void onSuccess(Response resp) { cf.complete(resp); } public void onError(Throwable t) { cf.completeExceptionally(t); } }); return cf; // returned still-incomplete; settles when the callback fires } ``` The caller gets a future immediately and composes on it; your callback later pushes the result in. This is the canonical way to turn any asynchronous callback-style API into the composable `CompletableFuture` world.

  • If two threads call complete() with different values on the same future, what value wins?
    Whichever call ran first and returned true; the other returns false and its value is discarded. Completion is a one-shot, first-wins transition.
  • How does a downstream thenApply stage behave when the future is completed exceptionally?
    thenApply is skipped entirely; the failure propagates down the chain until a handling stage (exceptionally/handle/whenComplete) is reached.

saying these in an interview costs you the question

  • Thinking complete() always succeeds — it returns false if already settled
  • Believing a second complete() overwrites the first value
  • Forgetting that join sees a CompletionException wrapper, not your raw exception
  • Assuming completeExceptionally throws — it only sets the state, never throws

context

open as a page

What is the difference between get() and join() on a CompletableFuture, and when would you choose each?

level: middleimportance: must knowfreq 75%

basics

~10 s

Both block until the result is ready. get() throws checked exceptions (InterruptedException, ExecutionException), so callers must handle them. join() throws unchecked exceptions (CompletionException), so it's cleaner inside lambdas and stream pipelines.

open as a page

How do you inspect a CompletableFuture's state without blocking, and what do getNow, isDone, isCancelled, and isCompletedExceptionally tell you?

level: middleimportance: should knowfreq 55%

basics

~20 s

These methods peek at the future without waiting. isDone tells you if it's finished (any way). isCancelled and isCompletedExceptionally tell you how it finished. getNow(default) returns the value right now if ready, otherwise your default — it never blocks.

open as a page

What do orTimeout and completeOnTimeout do, and how do they differ from each other and from get(timeout)?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Both (Java 9+) put a time limit on the future itself, not just on a waiting caller. orTimeout fails it with a TimeoutException if it's late; completeOnTimeout finishes it with a fallback value instead. Unlike get(timeout), they actually settle the future.

open as a page

What are the exact semantics of cancel() on a CompletableFuture, and what do obtrudeValue/obtrudeException do that you should rarely need?

level: principalimportance: nice to knowfreq 35%

basics

~20 s

cancel() on a CompletableFuture doesn't interrupt any thread — it just completes the future with a CancellationException; the mayInterruptIfRunning flag is ignored. obtrudeValue/obtrudeException forcibly overwrite an already-settled result, breaking the normal write-once rule — only for tests/recovery.

open as a page