Guide to CompletableFuture join() vs get()
Java’s CompletableFuture
is a powerful tool for asynchronous programming, offering various methods to handle and manipulate future tasks. Among these methods, join()
and get()
are two commonly used for waiting for the completion of a task and retrieving its result. Let us delve into understanding the key differences between CompletableFuture join vs get.
1. Overview of CompletableFuture
CompletableFuture is a part of Java’s java.util.concurrent
package, introduced in Java 8. A CompletableFuture
is a versatile tool in Java’s concurrency API that allows you to write non-blocking, asynchronous code. It provides methods to create, combine, and flexibly execute asynchronous tasks. By using CompletableFuture
, you can handle long-running computations without blocking the main thread, thus improving the performance and responsiveness of your application.
1.1 The join() Method
The join()
method is a part of the CompletableFuture
class and is used to wait for the completion of the asynchronous task. Unlike get()
, join()
does not throw a checked exception.
public T join()
The method waits if necessary for the computation to complete and then retrieves its result. If the computation completes exceptionally, join()
wraps the exception in an UncheckedExecutionException
and rethrows it.
1.2 The get() Method
The get()
method, also part of the CompletableFuture
class, is used to retrieve the result of the computation, blocking if necessary until the computation is complete.
public T get() throws InterruptedException, ExecutionException
This method waits if necessary for the computation to complete and then retrieves its result. If the computation completes exceptionally, get()
throws the checked exceptions InterruptedException
or ExecutionException
.
2. Comparison: join() vs. get()
While both join()
and get()
serve to wait for a CompletableFuture
to complete and retrieve its result, there are key differences:
- Exception Handling:
join()
throws an unchecked exception (CompletionException
), whileget()
throws checked exceptions (InterruptedException
andExecutionException
). - Usage Scenario: Use
join()
when you want to avoid handling checked exceptions, typically in a lambda or method reference context. Useget()
when you need to handle interruptions and other execution exceptions explicitly. - Blocking Behavior: Both methods block the calling thread until the computation is complete.
3. Code Example and Breakdown
Here is an example demonstrating the use of join()
and get()
:
import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CompletableFutureExample { public static void main(String[] args) { CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { throw new IllegalStateException(e); } return "Hello, World!"; }); // Using join() String resultJoin = future.join(); System.out.println("Result using join: " + resultJoin); // Using get() try { String resultGet = future.get(); System.out.println("Result using get: " + resultGet); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } }
In this example, a CompletableFuture
is created to run a task asynchronously. The task simulates a delay using Thread.sleep()
. The result is then retrieved using both join()
and get()
methods, demonstrating their usage and behavior.
3.1 Code Output and Explanation
Here is the output of the provided code example:
Result using join: Hello, World! Result using get: Hello, World!
3.1.1 Explanation
- Asynchronous Task:
- The code creates a
CompletableFuture
usingsupplyAsync()
, which runs a task asynchronously. - This task simulates a delay of 2 seconds using
Thread.sleep(2000)
, then returns the string “Hello, World!”.
- The code creates a
- Using
join()
:future.join()
is called, which waits for the asynchronous task to complete.- Once the task is complete,
join()
returns the result, “Hello, World!”, and prints it.
- Using
get()
:future.get()
is called, which also waits for the asynchronous task to complete.- Similar to
join()
,get()
returns the result, “Hello, World!”, and prints it. - If the task were interrupted or failed,
get()
would throwInterruptedException
orExecutionException
, respectively. However, in this case, the task was completed successfully.
3.2 Output When an Exception Occurs
Now let’s say if we modify the code to throw the exception within the asynchronous task, both join()
and get()
will handle it differently:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Task failed"); });
3.2.1 Output Using join()
The join()
method will throw an unchecked CompletionException
, which wraps up the original exception.
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.RuntimeException: Task failed at java.base/java.util.concurrent.CompletableFuture.join(CompletableFuture.java:2046) at CompletableFutureExample.main(CompletableFutureExample.java:10) Caused by: java.lang.RuntimeException: Task failed at CompletableFutureExample.lambda$main$0(CompletableFutureExample.java:7) at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700) ... 1 more
3.2.2 Output Using get()
The get()
method will throw ExecutionException
, which wraps up the original exception.
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: Task failed at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:389) at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2001) at CompletableFutureExample.main(CompletableFutureExample.java:15) Caused by: java.lang.RuntimeException: Task failed at CompletableFutureExample.lambda$main$0(CompletableFutureExample.java:7) at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700) ... 1 more
4. Conclusion
Both join()
and get()
methods of CompletableFuture
provide mechanisms to wait for the completion of an asynchronous task and retrieve its result. The choice between them depends on your specific use case and how you prefer to handle exceptions. Understanding these differences helps you write more robust and maintainable asynchronous code in Java.