skip to content

Async Method Execution (@Async)

@Async moves a method onto a TaskExecutor, returning void or a CompletableFuture, and choosing the right pool matters more than people expect. The self-invocation pitfall — an internal call that silently runs synchronously — is the usual follow-up.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

6

What does @Async do in Spring, and what must you do to switch it on?

level: juniorimportance: must knowfreq 78%

answer

  1. @Async = run on another thread
  2. dead without @EnableAsync
  3. proxy wraps the bean
  4. public method, called from outside
  5. void or Future/CompletableFuture only

basics

~10 s

@Async marks a method so Spring runs it on a separate thread instead of the caller's thread. It only works if you add @EnableAsync (or Boot's equivalent) to a configuration class first.

solid answer

~40 s

@Async tells Spring to execute the annotated method on a background thread from a TaskExecutor, so the caller returns immediately rather than blocking. Nothing happens unless async support is turned on with @EnableAsync on a @Configuration class (Spring Boot enables it once you add @EnableAsync anywhere). Under the hood Spring wraps the bean in a proxy: when you call the method, the proxy submits the real invocation to an Executor. Methods can return void (fire-and-forget) or a Future/CompletableFuture to hand back a result later. Because it is proxy-based, the annotation only applies to public methods invoked through the Spring-managed bean reference, not to calls a bean makes to itself.

code

java · 14 lines
java
@Configuration
@EnableAsync
public class AsyncConfig { }

@Service
public class NotificationService {

    // runs on a background thread; caller returns immediately
    @Async
    public void sendWelcomeEmail(String address) {
        // slow SMTP call happens off the request thread
        mailClient.send(address, "Welcome!");
    }
}

go deeper

for a junior

Must know @Async runs code on another thread and requires @EnableAsync.

for a middle

Should explain the proxy mechanism and the public-method / return-type rules.

for a senior

Should connect the proxy model to self-invocation and executor selection.

for a principal

Frames @Async as a proxy-woven concern with clear boundaries on when async side effects are safe versus when a synchronous transactional guarantee is required.

**What it is.** `@Async` (in package `org.springframework.scheduling.annotation`) is a method-level annotation that makes Spring run that method **asynchronously** — on a different thread than the one that called it. The caller does not wait for the method to finish; control returns immediately. **Turning it on.** The annotation is inert by itself. You must register the async infrastructure with `@EnableAsync` on any `@Configuration` class: ```java @Configuration @EnableAsync public class AsyncConfig { } ``` `@EnableAsync` imports infrastructure that registers an `AsyncAnnotationBeanPostProcessor`. This bean post-processor scans beans for `@Async` (on the class or its methods) and, if found, replaces the bean with a **proxy** (JDK dynamic proxy if the bean implements an interface, otherwise a CGLIB subclass). In Spring Boot you typically just add `@EnableAsync`; Boot then also auto-configures a real thread pool for it. **How a call flows.** When code holding a reference to the proxy calls the `@Async` method: (1) the proxy's advice (`AnnotationAsyncExecutionInterceptor`) intercepts the call, (2) it picks a `TaskExecutor`, (3) it submits the actual method body as a task to that executor, (4) it returns immediately to the caller — either `void`/`null`, or a `Future` handle the caller can poll later. **Return types.** Legal return types are `void`, `java.util.concurrent.Future<T>`, `java.util.concurrent.CompletableFuture<T>`, and Spring's `ListenableFuture<T>` (deprecated in Spring 6 in favor of `CompletableFuture`). Any *other* non-void return type is illegal for `@Async` — the interceptor cannot produce a meaningful value on a background thread while returning to the caller synchronously. **Key constraints (the proxy consequences).** - The method must be **public** in proxy mode; `@Async` on private/protected/package methods is silently ignored. - **Self-invocation** (a bean calling its own `@Async` method) bypasses the proxy and runs synchronously — a classic bug. - The bean must be a **Spring-managed bean**; `new`-ing the class gives you no proxy and no async behavior. **When to use.** Fire-and-forget side effects (sending email, writing an audit log, warming a cache) and parallelizing independent I/O calls. Do **not** use it for work that must complete before the response is returned to the user unless you keep the `Future` and join on it, and never for transactional units that the caller assumes committed synchronously. **Common gotcha.** Developers add `@Async`, see no threading, and are confused — usually they forgot `@EnableAsync`, called the method from within the same class, or made the method non-public.

  • You added @Async but the method still runs on the caller's thread. Name two likely causes.
    Most likely you forgot @EnableAsync, or you invoked the method from within the same bean (self-invocation bypasses the proxy). Other causes: the method isn't public, or the object was created with new instead of being a Spring bean.
  • Can an @Async method return a plain String?
    No. Allowed return types are void, Future, CompletableFuture, or the deprecated ListenableFuture. A plain value type is invalid because the proxy returns to the caller before the body has run.

saying these in an interview costs you the question

  • Thinking @Async works just by annotating the method, without @EnableAsync
  • Believing @Async can be on a private method
  • Assuming a self-call inside the same class runs asynchronously

context

open as a page

How does Spring decide which executor an @Async method uses, and how do you route a specific method to a specific pool?

level: seniorimportance: must knowfreq 58%

basics

~20 s

By default Spring looks for a single TaskExecutor bean, or one named 'taskExecutor', else falls back to SimpleAsyncTaskExecutor. To target a specific pool, put its bean name in the annotation: @Async("reportExecutor"). You can also set a global default via AsyncConfigurer.getAsyncExecutor().

open as a page

Explain the @Async self-invocation pitfall and the ways to work around it.

level: seniorimportance: must knowfreq 70%

basics

~20 s

@Async works through a proxy. If a bean calls its own @Async method directly (this.method()), the call skips the proxy and runs synchronously on the same thread. Fix it by calling through another bean, self-injecting the proxy, or using AspectJ mode.

open as a page

How do you configure the thread pool behind @Async, and what's the classic ThreadPoolTaskExecutor sizing gotcha?

level: middleimportance: should knowfreq 55%

basics

~10 s

Define a ThreadPoolTaskExecutor bean and tune corePoolSize, maxPoolSize, and queueCapacity. The gotcha: with an unbounded queue the pool never grows past corePoolSize, so maxPoolSize is effectively ignored until the queue is bounded.

open as a page

What return types can an @Async method have, and how do void vs CompletableFuture differ for the caller?

level: middleimportance: should knowfreq 60%

basics

~20 s

@Async methods can return void, Future, CompletableFuture (or the old ListenableFuture). With void it's fire-and-forget — the caller gets nothing back. With CompletableFuture the caller gets a handle to fetch the result or chain more work later.

open as a page

Compare @EnableAsync's PROXY mode with mode=ASPECTJ. When would you choose ASPECTJ, and what's the cost?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

PROXY (default) wraps beans in a proxy, so only external calls to public methods become async. ASPECTJ weaves the async advice into the bytecode, so self-invocation and non-public methods also work. ASPECTJ needs the AspectJ weaver and compile/load-time weaving setup, which is more complex.

open as a page