Understanding Spring Boot @Async: Usage, Pitfalls, and Best Practices

This article explains how to enable and configure Spring Boot's @Async annotation, demonstrates proper thread‑pool setup, highlights common mistakes such as calling async methods within the same class or mixing with @Transactional, and provides guidance on handling blocking tasks and exceptions for robust asynchronous execution.

Architecture Digest
Architecture Digest
Architecture Digest
Understanding Spring Boot @Async: Usage, Pitfalls, and Best Practices

@Async annotation is described as a secret weapon for performance optimization in Spring Boot projects, allowing methods to run in the background while the main thread continues without waiting for slower tasks.

Enabling @Async

To use @Async, add @EnableAsync to a configuration class or the main application class, which activates asynchronous behavior for all methods annotated with @Async.

@SpringBootApplication
@EnableAsync
public class BackendAsjApplication {
}

A ThreadPoolTaskExecutor bean can be defined to configure core pool size, max pool size, queue capacity, thread name prefix, and a rejection handler.

@Bean
public ThreadPoolTaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(2);
    executor.setMaxPoolSize(2);
    executor.setQueueCapacity(500);
    executor.setThreadNamePrefix("MyAsyncThread-");
    executor.setRejectedExecutionHandler((r, executor1) -> log.warn("Task rejected, thread pool is full and queue is also full"));
    executor.initialize();
    return executor;
}

Example service with an async method:

@Service
public class EmailService {
    @Async
    public void sendEmail() {
        // asynchronous code
    }
}

@Async Methods Must Reside in Different Classes

Calling an @Async method from the same class leads to a loop and can hang the application. The wrong approach is shown where PurchaseService calls its own @Async method. The correct approach places the async method in a separate service and injects it.

@Service
public class EmailService {
    @Async
    public void sendEmail() {
        // asynchronous code
    }
}

@Service
public class PurchaseService {
    @Autowired
    private EmailService emailService;

    public void purchase() {
        emailService.sendEmail();
    }
}

@Async and @Transactional Do Not Mix Well

@Transactional creates a proxy that runs within a transaction context, but an @Async method executes in a separate thread outside that context, so database operations inside the async method are not part of the transaction and may leave the database inconsistent if an exception occurs.

@Service
public class EmailService {
    @Transactional
    public void transactionalMethod() {
        // DB operation 1
        asyncMethod();
        // DB operation 2
    }

    @Async
    public void asyncMethod() {
        // DB operation 3 (outside transaction)
    }
}

@Async Blocking Issues

The default thread pool may run only a limited number of tasks concurrently (e.g., 2) with a queue capacity of 500. Long‑running tasks can block the pool, causing delays. A solution is to define separate executors for heavy and lightweight tasks.

@Primary
@Bean(name = "taskExecutorDefault")
public ThreadPoolTaskExecutor taskExecutorDefault() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(2);
    executor.setMaxPoolSize(2);
    executor.setQueueCapacity(500);
    executor.setThreadNamePrefix("Async-1-");
    executor.initialize();
    return executor;
}

@Bean(name = "taskExecutorForHeavyTasks")
public ThreadPoolTaskExecutor taskExecutorForHeavyTasks() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(2);
    executor.setMaxPoolSize(2);
    executor.setQueueCapacity(500);
    executor.setThreadNamePrefix("Async2-");
    executor.initialize();
    return executor;
}

Use the specific executor by naming it in the @Async annotation:

@Service
public class EmailService {
    @Async("taskExecutorForHeavyTasks")
    public void sendEmailHeavy() {
        // implementation
    }
}

Exception Handling in @Async Methods

Exceptions thrown inside an @Async method are not propagated to the calling thread. To handle them, either catch them inside the async method or return a Future and process the result with try‑catch.

@Service
public class EmailService {
    @Async
    public Future<String> sendEmail() throws Exception {
        throw new Exception("Oops, cannot send email!");
    }
}

@Service
public class PurchaseService {
    @Autowired
    private EmailService emailService;

    public void purchase() {
        try {
            Future<String> future = emailService.sendEmail();
            String result = future.get();
            System.out.println("Result: " + result);
        } catch (Exception e) {
            System.out.println("Caught exception: " + e.getMessage());
        }
    }
}

In summary, @Async is a powerful tool for improving performance and scalability in Spring Boot applications, but developers must be aware of its limitations regarding class boundaries, transaction management, thread‑pool configuration, and exception handling.

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.

transactionException HandlingAsynchronousSpring Bootthread poolAsync
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

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.