CompletableFuture<T> class: join() vs get()
Asked Answered
O

3

194

What is the difference between the get() and join() methods of the CompletableFuture<T> class?

Below is the my code:

List<String> process() {

    List<String> messages = Arrays.asList("Msg1", "Msg2", "Msg3", "Msg4", "Msg5", "Msg6", "Msg7", "Msg8", "Msg9",
            "Msg10", "Msg11", "Msg12");
    MessageService messageService = new MessageService();
    ExecutorService executor = Executors.newFixedThreadPool(4);

    List<String> mapResult = new ArrayList<>();

    CompletableFuture<?>[] fanoutRequestList = new CompletableFuture[messages.size()];
    int count = 0;
    for (String msg : messages) {
        CompletableFuture<?> future = CompletableFuture
                .supplyAsync(() -> messageService.sendNotification(msg), executor).exceptionally(ex -> "Error")
                .thenAccept(mapResult::add);

        fanoutRequestList[count++] = future;
    }

    try {
        CompletableFuture.allOf(fanoutRequestList).get();
      //CompletableFuture.allOf(fanoutRequestList).join();
    } catch (InterruptedException | ExecutionException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    return mapResult.stream().filter(s -> !s.equalsIgnoreCase("Error")).collect(Collectors.toList());
}

I have tried with both methods but I see no difference in result.

Oswaldooswalt answered 3/8, 2017 at 16:51 Comment(6)
get() requires you to catch checked exceptions. You should notice the difference when you change from get() to join(), as you will imediately get a compiler error saying that neither InterruptedException nor ExecutionException are thrown in the try block.Aspiration
@holi-java: join() can not get interrupted.Aspiration
@Aspiration yes, sir. I found I can't interrupt the task.Logsdon
@Holger, Thank you for reply and you are correct, get expects checked exception and join method not. But both are waiting for complete the all the future and I would like know whcih one I should use and why?Oswaldooswalt
Well get exists, because CompletableFuture implements the Future interface which mandates it. join() most likely has been introduced, to avoid needing to catch checked exceptions in lambda expressions when combining futures. In all other use cases, feel free to use whatever you prefer.Aspiration
Does it really make sense to use join or get as both block on the thread. Can't we instead make this asynchronous by using other composition methods to create a chain of asynchronous functions.ofvourse it depends on functionality. But in case of e.g. a service method in spring called by controller method returning completeable future it makes more sense to not call get or join in service method at all .does it?Bielefeld
U
196

The only difference is how methods throw exceptions. get() is declared in Future interface as:

V get() throws InterruptedException, ExecutionException;

The exceptions are both checked exceptions which means they need to be handled in your code. As you can see in your code, an automatic code generator in your IDE asked to create try-catch block on your behalf.

try {
  CompletableFuture.allOf(fanoutRequestList).get() 
} catch (InterruptedException | ExecutionException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

The join() method doesn't throw checked exceptions.

public T join()

Instead it throws unchecked CompletionException. So you do not need a try-catch block and instead you can fully harness exceptionally() method when using the disscused List<String> process function

CompletableFuture<List<String>> cf = CompletableFuture
    .supplyAsync(this::process)
    .exceptionally(this::getFallbackListOfStrings) // Here you can catch e.g. {@code join}'s CompletionException
    .thenAccept(this::processFurther);

You can find both get() and join() implementation here.

Unprofessional answered 23/11, 2017 at 21:3 Comment(1)
The part about exceptionally() is a bit distracting because it isn't relevant, as exceptionally(…) can be used with either get() or with join(). Perhaps you were trying to add additional commentary that exceptionally() is a different approach for handling CompletableFuture entirely; if so, it might help to clarify this and set it apart from the main answer. Nevertheless, there are even addition approaches to error handling with CompletableFuture, I'm not sure arbitrarily listing one of them really adds to the answer.Seamanlike
R
23

In addition to the answer provided by Dawid, the get method is available in two flavors:

get()
get(Long timeout, TimeUnit timeUnit) 

The second get takes wait time as an argument and waits for at most the provided wait time.

try {
    System.out.println(cf.get(1000, TimeUnit.MILLISECONDS));
} catch (InterruptedException | ExecutionException | TimeoutException ex) {
    ex.printStackTrace();
}

You can refer to this documentation for more information.

  1. join() is defined in CompletableFuture whereas get() comes from interface Future
  2. join() throws unchecked exception whereas get() throws checked exceptions
  3. You can interrupt get() and then throws an InterruptedException
  4. get() method allows to specify the maximum wait time
Rightism answered 8/12, 2021 at 17:50 Comment(1)
This is a good answer. While it's mostly recommended to use join one limitation of join is that you can't set timeout. get helps overcome this limitation.Suchta
S
3

The other answers talk about "one of the methods uses checked exceptions; the other does not", which is true, but misses some of the subtleties.

Based upon reading the documentation, let me try to break it down succinctly:

  • If the underlying task producing the value throws an exception, both get() and join() will throw a "wrapper exception" containing the thrown exception. In the case of get(), the wrapper exception will be an ExecutionException, which is a checked exception; in the case of join(), it will be a CompletionException, which is an unchecked exception. In either case, the wrapped exception (originating in the underlying task) will be the same.
  • It appears the join() cannot throw an InterruptedException; see CompletableFuture join vs get differences for sample code.
  • Either method can throw a CancellationException.

To summarize, join() is a "simplified" form of get() that doesn't throw InterruptedException, and returns its value in an unchecked CompletionException. If you use instead get(), you'll be forced not only to catch an ExecutionException (which will contain the same wrapped exception as CompletionException would), but also catch InterruptedException.

Seamanlike answered 19/10, 2023 at 21:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.