skip to content

How do you create a CompletableFuture that you complete manually later, and when is that useful?

level: middleimportance: should knowfreq 50%

answer

  1. new CompletableFuture<>() = empty, no task
  2. complete(value) / completeExceptionally(throwable)
  3. first completion wins; later calls return false
  4. bridge for callback/listener APIs
  5. orTimeout / completeOnTimeout guard against never-arriving events

basics

~10 s

Create an empty future with new CompletableFuture<>(), then call complete(value) when the result arrives, or completeExceptionally(error) on failure. It's useful for bridging callback-based APIs into the CompletableFuture world.

solid answer

~40 s

You allocate an incomplete future directly: new CompletableFuture<T>(). It has no task attached—anyone holding the reference can finish it later by calling complete(value) (success) or completeExceptionally(throwable) (failure). The first such call wins and unblocks every get() and downstream stage; later calls return false and are ignored. This is the standard way to adapt a callback- or listener-based API (Netty, a message-queue consumer, an async HTTP client) into a CompletableFuture: you hand back the future immediately, register a callback that calls complete from whatever thread fires it, and your caller composes on it normally. Related helpers include completeOnTimeout(value, time, unit) and orTimeout(time, unit) to auto-complete or fail if nothing happens in time. Because completion can come from any thread, treat the future as the thread-safe handoff point.

code

java · 9 lines
java
// Bridge a callback-style API into a CompletableFuture
CompletableFuture<String> fetch() {
    CompletableFuture<String> cf = new CompletableFuture<>();
    asyncClient.call(new Callback() {
        public void onSuccess(String body) { cf.complete(body); }
        public void onError(Throwable t)   { cf.completeExceptionally(t); }
    });
    return cf.orTimeout(5, java.util.concurrent.TimeUnit.SECONDS);
}

go deeper

for a junior

Knows you can create an empty future with new CompletableFuture<>() and finish it with complete(value).

for a middle

Adds completeExceptionally, the first-completion-wins rule, and the canonical callback-bridging use case.

for a senior

Handles both success and error paths, applies orTimeout/completeOnTimeout, and reasons about which thread completes it and thread-safety of the handoff.

for a principal

Designs adapter layers wrapping callback APIs as futures across a codebase, with consistent timeout/cancellation policy and backpressure considerations.

## Why manual completion exists Most CompletableFutures are born from a task: `supplyAsync` runs a `Supplier` and completes the future with its return value. But sometimes *there is no Supplier to run*. The value will arrive from somewhere you don't control on a thread you don't own—an OS callback, a network event, a message off a queue. For these, you need a future you can **complete by hand** when the event fires. ## Creating an incomplete future ```java CompletableFuture<String> cf = new CompletableFuture<>(); ``` This object is *valid but not done*. `cf.isDone()` is `false`, `cf.get()` blocks. No thread is working on it; it just sits waiting for someone to finish it. ## Completing it Three outcomes, from any thread: ```java cf.complete("result"); // success path cf.completeExceptionally(new IOException()); // failure path cf.cancel(true); // cancellation ``` - **`complete(value)`** sets the result and fires all dependent stages (the `thenApply`, `thenAccept`, etc. attached to `cf`). - **`completeExceptionally(throwable)`** finishes it in a failed state; dependents run their `exceptionally`/`handle` branches. - **Idempotency:** completion happens **exactly once**. The *first* successful `complete`/`completeExceptionally`/`cancel` wins and returns `true`; any later attempt returns `false` and is a no-op. This makes the future a safe one-shot signal even if two threads race to finish it. ## The canonical use: adapting a callback API Imagine an async HTTP client that takes a callback instead of returning a future: ```java CompletableFuture<Response> fetch(Request r) { CompletableFuture<Response> cf = new CompletableFuture<>(); client.sendAsync(r, new Callback() { public void onSuccess(Response resp) { cf.complete(resp); } public void onError(Throwable t) { cf.completeExceptionally(t); } }); return cf; // returned immediately, still incomplete } ``` The caller gets a normal `CompletableFuture` and can write `fetch(r).thenApply(...)`. The bridge is the manually-created future plus the callback that completes it. The callback may run on the client's I/O thread—that's fine, because completion is thread-safe and the downstream stages will be scheduled appropriately. ## Timeouts and auto-completion (Java 9+) ```java cf.orTimeout(5, TimeUnit.SECONDS); // fail with TimeoutException if not done cf.completeOnTimeout(fallback, 5, SECONDS); // complete with a fallback value instead ``` These guard a manually-completed future against an event that never arrives. ## Contrast with supplyAsync | | `new CompletableFuture<>()` + `complete` | `supplyAsync(supplier)` | |---|---|---| | Runs a task? | No | Yes, on an executor | | Who completes it? | You, explicitly | The framework, from the supplier's return | | Typical use | Bridge external callbacks/events | Run your own computation off-thread | ## Pitfalls - **Never completing it.** If no code path calls `complete`/`completeExceptionally`, every `get()` blocks forever. Always wire both the success *and* error callbacks, and consider `orTimeout`. - **Forgetting the exceptional path.** If the underlying operation can fail, you must call `completeExceptionally`, or failures silently hang the future. - **Assuming a thread.** Completion may come from an arbitrary callback thread; don't do heavy work in the completing call—offload via an async stage.

  • What happens if you call complete() twice on the same CompletableFuture?
    The first call wins and returns true; the second returns false and is ignored. Completion is a one-shot, exactly-once event.
  • How do you make a manually-completed future fail if the external event never arrives?
    Use orTimeout(time, unit) to fail it with a TimeoutException, or completeOnTimeout(fallback, time, unit) to complete it with a fallback value.

saying these in an interview costs you the question

  • Thinking a freshly new'd CompletableFuture is already complete or runs a task
  • Forgetting the exceptional path so failures hang the future forever
  • Believing multiple complete() calls each take effect (only the first wins)
  • Never completing it on some code path, leaving get() blocked permanently

context