Understanding Synchronous and Asynchronous Calls in Spring Boot with @Async and CompletableFuture

This article explains the difference between synchronous and asynchronous method calls in Spring Boot, demonstrates how to convert simple task methods to asynchronous ones using @Async, shows how to coordinate multiple async tasks with CompletableFuture, and provides complete code examples and test results.

Top Architect
Top Architect
Top Architect
Understanding Synchronous and Asynchronous Calls in Spring Boot with @Async and CompletableFuture

In Java applications, a synchronous call executes each line sequentially, waiting for the previous operation to finish, while an asynchronous call allows the program to continue without waiting for the result. The article first illustrates synchronous execution with three task methods (doTaskOne, doTaskTwo, doTaskThree) that each sleep for a random time up to 10 seconds and log their start and completion.

A unit test invokes these methods one after another, showing that the total execution time equals the sum of the three individual durations.

To improve performance when the tasks are independent, the article introduces Spring Boot's @Async annotation. By annotating each task method with @Async, the methods run concurrently in separate threads. The AsyncTasks class is updated accordingly, and the main application class is configured with @EnableAsync to enable asynchronous processing.

@Slf4j
@Component
public class AsyncTasks {
    public static Random random = new Random();

    @Async
    public void doTaskOne() throws Exception {
        log.info("开始做任务一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任务一,耗时:" + (end - start) + "毫秒");
    }

    @Async
    public void doTaskTwo() throws Exception {
        log.info("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任务二,耗时:" + (end - start) + "毫秒");
    }

    @Async
    public void doTaskThree() throws Exception {
        log.info("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任务三,耗时:" + (end - start) + "毫秒");
    }
}

Running the same unit test after adding @Async shows interleaved log output and a significantly reduced total runtime because the three tasks execute in parallel.

When the result of each asynchronous task is needed, the article shows how to change the methods to return CompletableFuture<String>. Each method returns a completed future after logging its execution time.

@Async
public CompletableFuture<String> doTaskOne() throws Exception {
    log.info("开始做任务一");
    long start = System.currentTimeMillis();
    Thread.sleep(random.nextInt(10000));
    long end = System.currentTimeMillis();
    log.info("完成任务一,耗时:" + (end - start) + "毫秒");
    return CompletableFuture.completedFuture("任务一完成");
}

The test method is then updated to capture the three CompletableFuture objects, wait for all of them with CompletableFuture.allOf(...).join(), and finally compute the total elapsed time.

@Test
public void test() throws Exception {
    long start = System.currentTimeMillis();
    CompletableFuture<String> task1 = asyncTasks.doTaskOne();
    CompletableFuture<String> task2 = asyncTasks.doTaskTwo();
    CompletableFuture<String> task3 = asyncTasks.doTaskThree();
    CompletableFuture.allOf(task1, task2, task3).join();
    long end = System.currentTimeMillis();
    log.info("任务全部完成,总耗时:" + (end - start) + "毫秒");
}

The final test run demonstrates that the three tasks finish concurrently, reducing the overall execution time from the sum of individual durations to roughly the longest single task duration.

Important notes: the asynchronous methods must not be declared static, otherwise @Async will not take effect; and the main application class must include @EnableAsync to activate the async infrastructure.

@EnableAsync
@SpringBootApplication
public class Chapter75Application {
    public static void main(String[] args) {
        SpringApplication.run(Chapter75Application.class, args);
    }
}

The complete source code is available in the chapter7-5 module of the SpringBoot-Learning repository on both GitHub and Gitee.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentCompletableFutureSpring BootAsync
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.