Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

CompletableFuture coordinates work through completion dependencies: one computation can transform, combine, or recover from another’s result. CyclicBarrier makes a fixed group of threads wait at the same point before proceeding. Use futures to build asynchronous pipelines; use a barrier when participating threads must rendezvous at repeated phase boundaries. They are different tools, and neither automatically provides the other’s behavior.

How do CompletableFuture and CyclicBarrier differ?

Decision point CompletableFuture / CompletionStage CyclicBarrier
What is coordinated? Completion of one or more computations Arrival of a fixed number of threads
Typical control flow Dependent transformations, combinations, and recovery Repeated synchronization points in parallel work
Does the caller block? Not when composing continuations; get() and join() wait for completion await() blocks each participating thread until the barrier trips
Execution control Stage actions may use the completing thread, the common pool, or a supplied executor, depending on the method Participating threads call await(); an optional barrier action runs on the final arriving thread
Failure behavior Exceptional completion propagates through dependent stages An interrupted, timed-out, or otherwise failed arrival can break the barrier for other waiters
Reuse Build additional stages or start additional operations The barrier can be reused after its waiting parties are released

These APIs can appear in one design, but they do not substitute for each other. A future represents a result or completion relationship; a barrier represents a group rendezvous.

How does CompletableFuture work?

CompletableFuture<T> is a Future that can be completed explicitly and also implements CompletionStage. A stage describes work that depends on an earlier stage. Instead of immediately waiting for a result, you can register a function, consumer, or action to run when its predecessor completes. See Oracle’s Java SE 26 CompletableFuture API and CompletionStage API.

Transform, consume, or follow a result

  • thenApply transforms a completed value and returns a stage with the new value.
  • thenAccept consumes the value without producing a new result.
  • thenRun runs an action without receiving the previous result.
  • thenCompose is for a function that itself returns a stage. It connects the nested stage into the pipeline rather than leaving you with a stage nested inside another stage.

For example, use thenApply to turn a user record into a display name. If looking up that user’s permissions returns another CompletableFuture, use thenCompose so the resulting pipeline follows the permissions lookup as well.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Combine independent work or wait for several operations

Start independent operations separately, then use thenCombine when both results are needed to produce a value. Use allOf(...) when the next step should wait until every supplied future completes. The allOf result is a CompletableFuture<Void>; it does not collect the individual values, so retrieve those from the original futures after the aggregate completes. anyOf(...) completes when one supplied future completes, with that future’s result or exception.

Choose where asynchronous work runs

supplyAsync starts a supplier that returns a value; runAsync starts a runnable that does not. Both have overloads that accept an Executor.

A method without the Async suffix, such as thenApply, does not promise a dedicated background thread: its dependent action may run in the thread that completes the current future or another thread calling a completion method. An async method without an explicit executor uses ForkJoinPool.commonPool() by default. An overload with an executor uses that executor. Choose deliberately when the task’s execution context matters; the API contract alone does not establish that a particular workload will run faster.

How do results, exceptions, and timeouts work?

Retrieve a result without confusing get and join

Both get() and join() wait for completion. Use get() when checked exception handling and interruption are part of the surrounding code: it can throw InterruptedException and reports exceptional completion through ExecutionException. Its timed overload can also throw TimeoutException. join() reports exceptional completion as an unchecked CompletionException; cancellation can produce CancellationException.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recover from or observe failure

  • exceptionally provides a recovery value or stage for exceptional completion.
  • handle runs for normal or exceptional completion and can compute a replacement result.
  • whenComplete runs for normal or exceptional completion to observe it, returning a stage that carries the same result or exception.

If a stage’s computation ends abruptly with an unchecked exception or error, dependent stages generally complete exceptionally with a CompletionException containing the cause.

Set a timeout outcome or fallback

orTimeout completes the future exceptionally with TimeoutException if the timeout elapses first. completeOnTimeout instead completes it with the supplied fallback value. Downstream stages therefore need to handle either an exception or a fallback result, according to the method used. delayedExecutor is also available for delayed submission.

Understand what cancellation does not guarantee

Calling cancel on a CompletableFuture is treated as exceptional completion with CancellationException. It does not directly control the computation responsible for completing the future, so cancellation is not a guarantee that the underlying work is forcibly stopped.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When should you use CyclicBarrier?

Use CyclicBarrier when a known number of participating threads must all reach the same point before any can continue. Each party calls await(); once the required parties arrive, they are released. The barrier can be reused for another phase, making it suitable for iterative parallel work—for example, workers process separate rows, rendezvous, then proceed to a shared merge phase. See Oracle’s Java SE 26 CyclicBarrier API.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Run one action at each barrier trip

You can provide a barrier action that runs once after the final party arrives and before the waiting threads are released. This is useful for phase-wide work such as merging worker results. If the action does not need to run while parties are suspended, await() returns an arrival index; one thread can use that index to perform a one-off action after the rendezvous.

Handle interruption, timeout, and broken barriers

If a party leaves a barrier point prematurely because of interruption, failure, or timeout, the barrier uses an all-or-none breakage model. Other waiting parties also leave abnormally, normally with BrokenBarrierException, unless they were interrupted at about the same time. Code using a barrier should handle interruption and broken-barrier cases, then decide whether the larger algorithm should stop or reset.

Know what synchronization the barrier establishes

For a successful trip, actions before a thread calls await() happen-before the barrier action, which happens-before actions following successful returns from the corresponding await() calls in other threads. This is a defined memory-consistency effect, not a general substitute for designing safe access to shared mutable state.

Can you use CompletableFuture and CyclicBarrier together?

Yes. Futures can represent asynchronous task completion while a barrier coordinates a fixed cohort of worker threads at a phase boundary. Keep the blocking behavior in view: await() blocks the thread that calls it. If barrier waits run inside tasks on a constrained executor, all available threads could become blocked at the barrier before tasks for the remaining parties start. This is a practical consequence of the barrier’s wait-for-all behavior and executor scheduling, not a special guarantee of either API. Ensure the execution arrangement lets every required party reach the barrier, or choose a nonblocking coordination design.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When is Phaser a better fit?

Oracle points to Phaser when the group needs capabilities such as a variable number of parties per cycle, alternate actions on exceptions, termination control, contention control, or status monitoring. A fixed reusable rendezvous is the simpler CyclicBarrier use case; do not choose it if the algorithm’s participation requirements call for those additional controls.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.