top of page

Differences between Future and CompletableFuture

Introduction In the realm of asynchronous and concurrent programming in Java, Future  and CompletableFuture  serve as essential tools for managing and executing asynchronous tasks. Both constructs offer ways to represent the result of an asynchronous computation, but they differ significantly in terms of functionality, flexibility, and ease of use. Understanding the distinctions between Future  and CompletableFuture  is crucial for Java developers aiming to design robust and efficient asynchronous systems. At its core, a Future  represents the result of an asynchronous computation that may or may not be complete. It allows developers to submit tasks for asynchronous execution and obtain a handle to retrieve the result at a later point. While Future  provides a basic mechanism for asynchronous programming, its capabilities are somewhat limited in terms of composability, exception handling, and asynchronous workflow management. On the other hand, CompletableFuture  introduces a more advanced and versatile approach to asynchronous programming in Java. It extends the capabilities of Future  by offering a fluent API for composing, combining, and handling asynchronous tasks with greater flexibility and control. CompletableFuture  empowers developers to construct complex asynchronous workflows, handle exceptions gracefully, and coordinate the execution of multiple tasks seamlessly. In this article, we will dive deeper into the differences between Future  and CompletableFuture , exploring their respective features, use cases, and best practices. By understanding the distinct advantages and trade-offs of each construct, developers can make informed decisions when designing asynchronous systems and leveraging concurrency in Java applications. Let's embark on a journey to explore the nuances of Future  and CompletableFuture  in the Java ecosystem. Use Cases for Future Parallel Processing: Use Future  to parallelize independent tasks across multiple threads and gather results asynchronously. For example, processing multiple files concurrently. Asynchronous IO: When performing IO operations that are blocking, such as reading from a file or making network requests, you can use Future  to perform these operations in separate threads and continue with other tasks while waiting for IO completion. Task Execution and Coordination: Use Future  to execute tasks asynchronously and coordinate their completion. For example, in a web server, handle multiple requests concurrently using futures for each request processing. Timeout Handling: You can set timeouts for Future  tasks to avoid waiting indefinitely for completion. This is useful when dealing with resources with unpredictable response times. Use Cases for CompletableFuture Async/Await Pattern: CompletableFuture  supports a fluent API for chaining asynchronous operations, allowing you to express complex asynchronous workflows in a clear and concise manner, similar to the async/await pattern in other programming languages. Combining Results: Use CompletableFuture  to combine the results of multiple asynchronous tasks, either by waiting for all tasks to complete ( allOf ) or by combining the results of two tasks ( thenCombine , thenCompose ). Exception Handling: CompletableFuture  provides robust exception handling mechanisms, allowing you to handle exceptions thrown during asynchronous computations gracefully using methods like exceptionally  or handle . Dependency Graphs: You can build complex dependency graphs of asynchronous tasks using CompletableFuture , where the completion of one task triggers the execution of another, allowing for fine-grained control over the execution flow. Non-blocking Callbacks: CompletableFuture  allows you to attach callbacks that are executed upon completion of the future, enabling non-blocking handling of results or errors. Completing Future Manually: Unlike Future , you can complete a CompletableFuture  manually using methods like complete , completeExceptionally , or cancel . This feature can be useful in scenarios where you want to provide a result or handle exceptional cases explicitly. Examples Creation and Completion Future code example of creation and completion. ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
return 10;
}); CompletableFuture code example of creation and completion. CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
} catch (InterruptedException e) {
return 10;
}); In CompletableFuture , supplyAsync  method allows for asynchronous execution without the need for an external executor service an shown in the first example. Chaining Actions Example below in how to chain actions using Future. Future<Integer> future = executor.submit(() -> 10);
Future<String> result = future.thenApply(i -> "Result: " + i); Now, an example using CompletableFuture in how to chain actions. CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<String> result = completableFuture.thenApply(i -> "Result: " + i); CompletableFuture  offers a fluent API ( thenApply , thenCompose , etc.) to chain actions, making it easier to express asynchronous workflows. Exception Handling Handling exception using Future Future<Integer> future = executor.submit(() -> {
throw new RuntimeException("Exception occurred");
}); Handling exception using CompletableFuture CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Exception occurred");
}); CompletableFuture  allows for more flexible exception handling using methods like exceptionally  or handle . Waiting for Completion // Future
Integer result = future.get();

// CompletableFuture
Integer result = completableFuture.get();
Both Future  and CompletableFuture  provide the get()  method to wait for the completion of the computation and retrieve the result. Combining Multiple CompletableFutures CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);

CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20);

CompletableFuture<Integer> combinedFuture = future1.thenCombine(future2, (x, y) -> x + y); CompletableFuture  provides methods like thenCombine , thenCompose , and allOf  to perform combinations or compositions of multiple asynchronous tasks. Conclusion In the dynamic landscape of asynchronous and concurrent programming in Java, both Future  and CompletableFuture  stand as indispensable tools, offering distinct advantages and use cases. While Future  provides a basic mechanism for representing the result of asynchronous computations, its capabilities are somewhat limited when it comes to composability, exception handling, and asynchronous workflow management. On the other hand, CompletableFuture  emerges as a powerful and flexible alternative, extending the functionalities of Future  with a fluent API for composing, combining, and handling asynchronous tasks with greater control and elegance. The choice between Future  and CompletableFuture  hinges on the specific requirements and complexities of the task at hand. For simple asynchronous operations or when working within the confines of existing codebases, Future  may suffice. However, in scenarios that demand more sophisticated asynchronous workflows, exception handling, or task coordination, CompletableFuture  offers a compelling solution with its rich feature set and intuitive API.

Differences between Future and CompletableFuture
bottom of page