skip to content

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