skip to content

CompletableFuture Creation

The entry points — completedFuture, supplyAsync, runAsync, and a manually completed future — and the fact that async methods default to the common ForkJoinPool unless you pass an executor. That default is where most CompletableFuture production problems begin.

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

questions

5

What are the main ways to create a CompletableFuture that runs a task asynchronously, and how do supplyAsync and runAsync differ?

level: juniorimportance: must knowfreq 70%

answer

  1. supply = Supplier = value; run = Runnable = Void
  2. completedFuture = already done, no task
  3. new CompletableFuture<>() + complete() = manual
  4. default executor = common ForkJoinPool
  5. every async method has an Executor overload

basics

~10 s

Use CompletableFuture.supplyAsync when the task returns a value, and runAsync when it returns nothing. supplyAsync takes a Supplier and gives a CompletableFuture<T>; runAsync takes a Runnable and gives a CompletableFuture<Void>.

solid answer

~40 s

There are three common creation paths. CompletableFuture.completedFuture(value) builds one that is already finished with a known value—no task runs. CompletableFuture.supplyAsync(Supplier) runs a value-producing task on a background thread and returns a CompletableFuture<T> that completes with the supplier's result. CompletableFuture.runAsync(Runnable) runs a side-effecting task with no result and returns a CompletableFuture<Void>. The key distinction is the return: supplyAsync's Supplier produces a value you can later transform with thenApply etc.; runAsync's Runnable produces nothing, so you only chain thenRun or thenAccept-style steps that don't need an input. Both run on the common ForkJoinPool by default, but each has an overload taking a custom Executor. You can also create an empty new CompletableFuture<>() and complete it manually later.

go deeper

for a junior

Knows supplyAsync returns a value (Supplier) and runAsync returns nothing (Runnable), and can pick the right one.

for a middle

Adds completedFuture and manual new+complete; explains the CompletableFuture<Void> consequence for chaining (thenRun vs thenApply).

for a senior

Notes the default common ForkJoinPool and the Executor overloads, and why blocking tasks need a dedicated executor.

for a principal

Reasons about pool sizing/starvation across the JVM, async API contracts (when to expose a CompletableFuture-returning method), and bridging external callbacks via manual completion.

## What a CompletableFuture is A `Future<T>` in Java represents a value that will be available *later*—the result of a computation running, typically, on another thread. The original `java.util.concurrent.Future` (Java 5) was limited: you could only `get()` (which blocks) or `cancel()`. You could not say "when this finishes, do that" without blocking a thread. `CompletableFuture<T>` (Java 8, in `java.util.concurrent`) fixes this. It implements `Future<T>` *and* `CompletionStage<T>`. "Completion stage" means you can attach **callbacks** that fire when it completes, building a non-blocking pipeline (`thenApply`, `thenCompose`, etc.). This question is only about how you **create** one. ## Term definitions - **Supplier<T>**: a functional interface with one method `T get()`—takes nothing, returns a value. Written as a lambda `() -> computeSomething()`. - **Runnable**: a functional interface with `void run()`—takes nothing, returns nothing (a side effect only). - **Executor**: an object that runs submitted tasks. It decides *which thread* runs the work. - **ForkJoinPool.commonPool()**: a shared, JVM-wide thread pool sized by default to (CPU cores − 1) threads. It is the *default* executor for `CompletableFuture`'s async methods. ## The creation methods ### 1. `completedFuture(value)` ```java CompletableFuture<String> cf = CompletableFuture.completedFuture("hello"); ``` No task runs. The future is born *already complete* with the given value. `cf.get()` returns immediately. Useful for returning a constant from a method whose signature is async, or as a starting point in tests. ### 2. `supplyAsync(Supplier<T>)` — produces a result ```java CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> 2 + 2); ``` The supplier runs on a background thread (the common pool by default). When it returns, the future completes with that value (here `4`). Because there is a result, you can transform it: `cf.thenApply(x -> x * 10)`. ### 3. `runAsync(Runnable)` — no result ```java CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> log.info("done")); ``` The runnable runs on a background thread but produces nothing, so the type is `CompletableFuture<Void>`. You chain steps that don't need an input value: `cf.thenRun(() -> ...)`. There is no value to feed into a `thenApply`. ### 4. Manual completion ```java CompletableFuture<String> cf = new CompletableFuture<>(); // ... later, from any thread/callback: cf.complete("value"); // or cf.completeExceptionally(ex) ``` You create an *empty, incomplete* future and complete it yourself when an external event (a callback, a message, a timer) arrives. This is the bridge between callback-style APIs and the CompletableFuture world. ## supplyAsync vs runAsync — the core difference | | `supplyAsync` | `runAsync` | |---|---|---| | Argument | `Supplier<T>` | `Runnable` | | Result type | `CompletableFuture<T>` | `CompletableFuture<Void>` | | Has a value? | Yes | No | | Next steps | `thenApply`, `thenCompose`… | `thenRun` | Choose by whether the work *yields a value you need downstream* (supply) or is a *fire-and-forget side effect* (run). ## Executor selection Every async creator has two overloads: ```java supplyAsync(supplier) // common ForkJoinPool supplyAsync(supplier, myExecutor) // your pool ``` The common pool is fine for short CPU-bound work but is **shared across the whole JVM**; long or blocking tasks (I/O, JDBC, sleeps) can starve it and stall unrelated code. For blocking work, pass a dedicated `Executor`.

  • After runAsync, why can't you call thenApply on the result?
    runAsync yields a CompletableFuture<Void>—there is no value to transform. thenApply needs an input value, so you use thenRun (no input) instead.
  • When would you use completedFuture instead of supplyAsync?
    When the value is already known and no computation is needed—e.g. returning a cached/constant result from a method whose signature must be async, or seeding a pipeline/test.

saying these in an interview costs you the question

  • Saying runAsync returns a value or takes a Supplier
  • Thinking supplyAsync blocks the calling thread until the result is ready
  • Believing each async call spins up a brand-new thread rather than using a shared pool
  • Confusing completedFuture (no task) with supplyAsync (runs a task)

context

open as a page

On which thread pool do supplyAsync and runAsync run by default, and how do you override it?

level: middleimportance: must knowfreq 65%

basics

~20 s

By default they run on the shared ForkJoinPool.commonPool(). Each async method also has an overload that takes an Executor, so you pass your own thread pool as a second argument to control where the task runs.

open as a page

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

level: middleimportance: should knowfreq 50%

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.

open as a page

If the Supplier passed to CompletableFuture.supplyAsync throws an exception, what happens to the future and to a later get() call?

level: seniorimportance: should knowfreq 55%

basics

~10 s

The exception is captured and the future completes exceptionally—it does not propagate out of supplyAsync. A later get() then throws an ExecutionException whose cause is the thrown exception; join() throws a CompletionException wrapping it.

open as a page

Why might you choose CompletableFuture.supplyAsync over ExecutorService.submit, given both run a task on a thread pool and return a Future?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Both run a task and return a Future, but ExecutorService.submit gives a plain Future you can only block on with get(). supplyAsync returns a CompletableFuture you can chain non-blocking steps onto (thenApply, thenCompose, combine) and add error recovery.

open as a page