Async Methods

Why Async

After a customer places an order, ShopEase sends an email, an SMS and updates analytics. The customer should not wait for all of that. @Async runs a method on another thread and returns immediately.

@SpringBootApplication
@EnableAsync
public class ShopEaseApplication { ... }

@Service
@Slf4j
public class NotificationService {

    @Async
    public void sendOrderEmail(String email, Long orderId) {
        log.info("Sending email for order {}", orderId);
        // call email provider (slow, ~800 ms)
    }

    @Async
    public CompletableFuture<Boolean> sendSms(String phone, String text) {
        // call SMS provider
        return CompletableFuture.completedFuture(true);
    }
}

Example

The request thread and the async thread are different:

curl -X POST localhost:8080/api/orders ...

Example output

INFO [nio-8080-exec-3] OrderController     : Order 1001 placed, responding 201
INFO [ shopease-async-1] NotificationService : Sending email for order 1001

Rules for @Async

  • Return void or CompletableFuture<T>.
  • The method must be public and called from another bean (proxy rule, see the AOP module).
  • Exceptions in void async methods never reach the caller: configure an AsyncUncaughtExceptionHandler.
  • Spring Boot auto-configures a ThreadPoolTaskExecutor (8 core threads), or a virtual-thread executor when spring.threads.virtual.enabled=true.

What @Async Does Under the Hood

Example

An @Async call is just a task submitted to an executor. Here checkout fetches price, stock and coupon in parallel (200 ms each) and joins the results:

import java.util.concurrent.*;

public class Main {
    static String slow(String what, String value) {
        try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        return what + "=" + value;
    }

    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        long start = System.nanoTime();

        CompletableFuture<String> price  = CompletableFuture.supplyAsync(() -> slow("price", "54999"), pool);
        CompletableFuture<String> stock  = CompletableFuture.supplyAsync(() -> slow("stock", "12"), pool);
        CompletableFuture<String> coupon = CompletableFuture.supplyAsync(() -> slow("coupon", "SAVE10"), pool);

        CompletableFuture.allOf(price, stock, coupon).join();
        long ms = (System.nanoTime() - start) / 1_000_000;

        System.out.println(price.get() + ", " + stock.get() + ", " + coupon.get());
        System.out.println("Finished in under 500 ms (not 600): " + (ms < 500));
        pool.shutdown();
    }
}

Output

price=54999, stock=12, coupon=SAVE10
Finished in under 500 ms (not 600): true