Sep 14

Difference Between CompletableFuture And Future In Java

Asynchronous programming is a critical skill for modern developers, especially in a world where responsiveness and scalability are paramount. In Java, two powerful tools for managing asynchronous tasks are Future and CompletableFuture. Both provide ways to handle asynchronous tasks, but they differ significantly in features and flexibility. In this post, we’ll explore the core differences and see how CompletableFuture overcomes some of the limitations of Future.

Future was introduced in Java 5 and has very limited functionality.

CompletableFuture was introduced in Java 8 with additional features like option to chain function calls onto the result of the initial task.

1. Blocking vs Non-Blocking

  • Future: When using Future, you submit a task to an ExecutorService, and it returns a Future object. However, to get the result of the computation, you must call the get() method, which blocks the calling thread until the task is complete. This can make your application less responsive, as it waits for the computation to finish.

  • CompletableFuture: With CompletableFuture, you get non-blocking execution. It allows you to define what happens when the computation is done through methods like thenApply(), thenAccept(), and thenRun(). This makes your application much more responsive, as it doesn't block the main thread while waiting for the result.

2. Callback Support

  • Future: Unfortunately, Future doesn't allow you to attach callbacks that define what to do when the computation is done. You must block the thread with get() and manually process the result later.

  • CompletableFuture: On the other hand, CompletableFuture provides full support for chaining tasks. You can attach various callbacks using thenApply(), thenAccept(), or thenRun() to define what should happen after the task completes, without blocking the main thread.



3. Exception Handling

  • Future: If the task fails, Future provides no built-in mechanism to handle exceptions. You must catch exceptions when you call get(), making error handling a bit clunky.

  • CompletableFuture: Offers much more robust exception handling with methods like exceptionally() and handle(). These methods allow you to define fallback actions or recovery mechanisms within the same async computation chain, ensuring smoother error management.


4. Composing Results

  • Future: One of the major limitations of Future is the inability to combine multiple Future instances or chain tasks. If you need to wait for multiple tasks to complete, you have to manage it manually, which can be cumbersome.

  • CompletableFuture: This is where CompletableFuture excels. It allows you to compose and combine asynchronous tasks easily using methods like thenCompose(), thenCombine(), or run multiple tasks in parallel with allOf(). This makes complex workflows far easier to handle.


5. Asynchronous Execution Support

  • Future: To run tasks asynchronously, Future needs to be paired with an ExecutorService, which adds some overhead. While this approach works for simple scenarios, it lacks the flexibility of more advanced async handling.

  • CompletableFuture: With CompletableFuture, asynchronous execution is built-in. Methods like supplyAsync() and runAsync() allow you to run tasks asynchronously without needing to explicitly manage an ExecutorService.


6. Use Cases

  • Future: Use Future for simple use cases where you need to execute a task in another thread and block until you retrieve the result. It's helpful for basic concurrency but lacks advanced features.

  • CompletableFuture: If you need to handle more complex asynchronous workflows with non-blocking behavior, error handling, and task composition, CompletableFuture is the way to go. It offers a more complete solution for modern async programming in Java.

Below is the summary of methods available in Completable Future:

  • supplyAsync: Executes a task asynchronously and returns the result in a CompletableFuture.
  • runAsync: Executes a task asynchronously without returning any result.
  • thenApply: Transforms the result of a CompletableFuture once it completes.
  • thenAccept: Consumes the result of a CompletableFuture after completion without returning a value.
  • thenRun: Runs a runnable task after the CompletableFuture completes without using its result.
  • thenCompose: Chains two CompletableFuture tasks sequentially by using the result of the first to trigger the second.
  • thenCombine: Combines results of two independent CompletableFuture tasks and processes them together.
  • allOf: Waits for all CompletableFuture tasks to complete and combines them.
  • anyOf: Waits for the first CompletableFuture task to complete, then proceeds.
  • exceptionally: Handles exceptions in the async pipeline by providing a fallback value.
  • handle: Processes both the result and any exceptions from a CompletableFuture.
  • complete: Manually completes the CompletableFuture with a result.
  • completeExceptionally: Completes the CompletableFuture exceptionally with a given error.
  • join: Similar to get(), but throws unchecked exceptions, making it more convenient to use in async chains.
  • orTimeout: Sets a timeout for the completion of the CompletableFuture and triggers an exception if exceeded.
  • completeOnTimeout: Completes the CompletableFuture with a default value if the task takes too long.

  • Created with