43

What's the advantage of using Spring Async vs. Just returning the CompletableFuture on your own?

Didier L
  • 18,905
  • 10
  • 61
  • 103
Christian Bongiorno
  • 5,150
  • 3
  • 38
  • 76
  • Maybe [THIS](https://stackoverflow.com/questions/533783/why-is-spawning-threads-in-java-ee-container-discouraged) will help you. – Flown Jun 22 '17 at 05:16
  • 1
    This might have made sense in pre java 8. But CompletableFuture by default forks onto the fork join pool. – Christian Bongiorno Jun 22 '17 at 05:28
  • 5
    You've got a managed environment. It is discouraged to spawn threads on your own, but if you have to, then you can use the `CompletableFuture` with an injected `ExecutorService` which should be managed by your container. – Flown Jun 22 '17 at 06:03
  • 1
    Offer it as an answer and let's if you get upvoted. – Christian Bongiorno Jun 22 '17 at 06:47

2 Answers2

37

There is no “vs.” between the two – these are complementary technologies:

  • CompletableFuture provides a convenient way to chain different stages of asynchronous computation – with more flexibility than Spring's ListenableFuture;
  • @Async provides convenient management of your background tasks and threads, with standard Spring configuration for your executor(s).

But both can be combined (since Spring 4.2). Suppose you want to turn the following method into a background task returning a CompletableFuture:

public String compute() {
    // do something slow
    return "my result";
}

What you have to do:

  • if not already done: configure your application with @EnableAsync and an Executor bean
  • annotate the method with @Async
  • wrap its result into CompletableFuture.completedFuture()
@Async
public CompletableFuture<String> computeAsync() {
    // do something slow - no change to this part
    // note: no need to wrap your code in a lambda/method reference,
    //       no need to bother about executor handling
    return CompletableFuture.completedFuture("my result");
}

As you notice, you don't have to bother about submitting the background task to an executor: Spring takes care of that for you. You only have to wrap the result into into a completed CompletableFuture so that the signature matches what the caller expects.

In fact, this is equivalent to:

@Autowire
private Executor executor;

public CompletableFuture<String> computeAsync() {
    return CompletableFuture.supplyAsync(() -> {
        // do something slow
        return "my result";
    }, executor);
}

but it removes the need to:

  • inject the executor
  • deal with the executor in a supplyAsync() call
  • wrap the logic in a lambda (or extract it to a separate method)
Didier L
  • 18,905
  • 10
  • 61
  • 103
  • 1
    I don’t see how this is a better way than the one in the accepted answer. There’s less magic there. What am I missing? – Balázs Németh Mar 27 '20 at 18:59
  • @BalázsNémeth, not sure which one you are refirring to with “_less magic there_”, but I assume it is the one in the accepted answer. Indeed if you don't like the _magic_, as usual the programmatic version will be better for you. However people usually consider declarative programming better, which is the case when using Spring's `@Async`. Of course, that involves some “_magic_” ;) – Didier L May 07 '20 at 15:37
  • I see your point. @Async is a higher level concept. I should probably trust it but I somehow don't :) – Balázs Németh May 07 '20 at 16:20
  • 1
    @BalázsNémeth One thing I particularly appreciate with `@Async` is that you don't have to specify the executor, but you are still able to customize it through Spring's `@EnableAsync` configuration. With `supplyAsync()`, either you use the common F-J pool (probably not desirable), or you have to pass the executor parameter every time you use it. – Didier L May 07 '20 at 16:27
  • One gotcha about `@Async` is if the method is called from within the same class, the call will not be async. It's only when it's called from a different class that spring gets to inject the proxied bean and intercept the call and submit it to a different thread. No such issue with `CompletableFuture.supplyAsync` with explicit executor service (which can be wired in by spring). – avmohan Dec 03 '21 at 05:59
  • Does anyone know why we use ```completedFuture``` method here and not ```supplyAsync( () -> "myResylt")``` ? Because it is not quite clear which one is preferable. – privalou Dec 18 '21 at 23:48
  • 1
    @privalou `supplyAsync()` would schedule your supplier for execution and make it run on a separate thread, which would be useless since there would actually be nothing to compute. In fact `completedFuture()` is the non-async equivalent, it’s just a convenient way to wrap a value in a `CompletableFuture`. `@Async` makes Spring deal with the whole async logic for you. I have add the equivalent code in the answer if you weren’t using `@Async`, hope this helps. – Didier L Dec 19 '21 at 14:17
  • 1
    @DidierL: Does that mean, spring @Async overrides the behavior of Java8's [completedFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#completedFuture-U-) whose purpose is to return `CompletableFuture` of an already computed result. So, Spring's @Async will resolve the code snippet i.e., "my result" and pass it to `supplyAysnc()` lambda? If yes why pass it to thread when result is already computed. Please clarify and correct me here if I am wrong – srk Dec 19 '21 at 14:42
  • @srk Spring does not modify the implementation of your method or the `CompletableFuture` class. Instead, it will wrap the calls to your method such that they are executed asynchronously, and immediately return an uncompleted `CompletableFuture`. When your method returns, the result will be passed back to that future to make it available to the original caller. – Didier L Dec 19 '21 at 16:21
22

Your application is managed by the container. Since it's discouraged to spawn Threads on you own, you can let the container inject a managed Executor.

@Service
class MyService {
  @Autowired
  private Executor executor;

  public CompletableFuture<?> compute() {
    return CompletableFuture.supplyAsync(() -> /* compute value */, executor);
  }
}
Flown
  • 11,480
  • 3
  • 45
  • 62