javaspringspring-bootcompletable-futurespring-async

How are Spring boot @Async methods actually async/non-blocking?


The following example is taken from Spring' Getting Started Creating Asynchronous Methods.

@Service
public class GitHubLookupService {

  @Async
  public CompletableFuture<User> findUser(String user) throws InterruptedException {
    logger.info("Looking up " + user);
    String url = String.format("https://api.github.com/users/%s", user);
    User results = restTemplate.getForObject(url, User.class);
    // Artificial delay of 1s for demonstration purposes
    Thread.sleep(1000L);
    return CompletableFuture.completedFuture(results);
  }

}

AFAIK my knowledge of async methods in computer science goes - It should return immediately. It should be non-blocking.

So lets say somewhere in Spring lets say my code findUser() is called like so:

CompletableFuture<User> user = service.findUser("foo");

This would actually block. It would block a different thread on the Executor service but it would block due to the Thread.sleep(1000L). Correct ?

So how is this async ?

I mean the whole point of CompletableFuture is to get a reference to a computation that will be completed in the future. But here when I get back the completed future the computation is already over, ie. we are using CompletableFuture.completedFuture(results).

So what is the point of having a CompletableFuture in this case ? I mean if I am going to block and return only when my computation is over and I have the results, I might as well just return the result and not the CompletableFuture.

How is this truly non-blocking/async ?

The only non-blocking aspect I find here is offload to a different thread, nothing else.

Am I going wrong somewhere ? What am I missing ?

Thanks.


Solution

  • The problem lies in the way you create your Future. The code you use is

    CompletableFuture.completedFuture(results)
    

    Quoting from the JavaDoc, this is only a wrapper between sync and async, where the computation was done synchronously:

    Returns a new CompletableFuture that is already completed with the given value.

    This is useful in certain situations where you only want to do asynchronous work for some inputs. Consider

    (x) -> x==0 ? CompletableFuture.completedFuture(0) : CompletableFuture.supplyAsync(expensiveComputation)
    

    I hope this makes the difference clear - if you want truly async computations, you need to use the supplyAsync function:

    Returns a new CompletableFuture that is asynchronously completed by a task running in the ForkJoinPool.commonPool() with the value obtained by calling the given Supplier.