How Does @Async Work Internally in Spring Boot?

Published: (January 10, 2026 at 01:10 AM EST)
4 min read
Source: Dev.to

Source: Dev.to

Introduction 🚀

Have you ever called a REST API and thought:

“Why is this request blocking until everything finishes?”

Now imagine sending an email, generating a report, or calling a slow third‑party service — do you really want your user to wait?

That’s where @Async in Spring Boot comes to the rescue.

In simple terms, @Async lets Spring say:

“You go ahead, I’ll handle this task in the background.”

Behind this simple annotation, Spring Boot uses proxies, thread pools, and executors to run your code asynchronously — and understanding how it works internally helps you avoid production bugs and interview traps.

In this blog, you’ll learn:

  • What @Async really does behind the scenes
  • How Spring Boot executes async methods
  • Two complete, end‑to‑end Java 21 examples
  • Common mistakes and best practices

Core Concepts 🧠

What Is @Async in Spring Boot?

@Async is a Spring annotation that allows a method to:

  • Run in a separate thread
  • Return immediately to the caller
  • Execute logic asynchronously using a TaskExecutor

Think of it like ordering food online 🍔:

  1. You place the order (API call)
  2. The restaurant prepares it in the background
  3. You don’t stand at the counter waiting

How @Async Works Internally (Simple Explanation)

Internally, Spring Boot uses Spring AOP (proxy‑based mechanism).

  1. Spring creates a proxy for your bean.
  2. When an @Async method is called, the proxy intercepts the call.
  3. The proxy submits the method to a TaskExecutor.
  4. The executor runs the method in a separate thread.
  5. The main thread continues immediately.

Key takeaway: @Async works only when the method is called from another Spring‑managed bean.

Use Cases & Benefits

✅ Best use cases

  • Sending emails
  • Calling slow external APIs
  • Background processing
  • Event handling
  • Audit logging

🎯 Benefits

  • Faster API responses
  • Better user experience
  • Cleaner separation of concerns

End‑to‑End Setup (Java 21 + Spring Boot) ⚙️

We’ll build:

  • A REST API
  • An async service
  • A custom thread pool
  • cURL request + response

Example 1: Basic @Async Execution

1️⃣ Enable Async Support

@Configuration
@EnableAsync
public class AsyncConfig {
}

This tells Spring to look for @Async methods.

2️⃣ Async Service

@Service
public class NotificationService {

    @Async
    public void sendNotification() throws InterruptedException {
        // Simulate long‑running task
        Thread.sleep(3000);
        System.out.println("Notification sent by thread: " +
                           Thread.currentThread().getName());
    }
}

3️⃣ REST Controller

@RestController
@RequestMapping("/api")
public class NotificationController {

    private final NotificationService service;

    public NotificationController(NotificationService service) {
        this.service = service;
    }

    @GetMapping("/notify")
    public String triggerNotification() throws InterruptedException {
        service.sendNotification();
        return "Request accepted. Processing asynchronously.";
    }
}

4️⃣ cURL Request

curl -X GET http://localhost:8080/api/notify

✅ Response

Request accepted. Processing asynchronously.

The API returns immediately, while the task runs in the background.

Why use this?

  • Better control
  • Non‑blocking result handling
  • Cleaner async composition

1️⃣ Async Service with Return Value

@Service
public class ReportService {

    @Async
    public CompletableFuture<String> generateReport() throws InterruptedException {
        Thread.sleep(2000);
        return CompletableFuture.completedFuture("Report generated successfully");
    }
}

2️⃣ REST Controller

@RestController
@RequestMapping("/api")
public class ReportController {

    private final ReportService service;

    public ReportController(ReportService service) {
        this.service = service;
    }

    @GetMapping("/report")
    public CompletableFuture<String> generate() throws InterruptedException {
        return service.generateReport();
    }
}

3️⃣ cURL Request

curl -X GET http://localhost:8080/api/report

✅ Response

Report generated successfully

The request thread is released early, while the task runs asynchronously.

@Configuration
@EnableAsync
public class AsyncExecutorConfig {

    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-worker-");
        executor.initialize();
        return executor;
    }
}

Without this, Spring falls back to a SimpleAsyncTaskExecutor, which is not production‑friendly.

Best Practices ✅

  • Never call @Async inside the same class – self‑invocation bypasses Spring proxies.
  • Always define a custom executor – avoid unbounded thread creation.
  • Use CompletableFuture for return values – gives you better async handling and composition.
  • Keep async logic idempotent and exception‑safe; handle failures inside the async method.
  • Monitor thread‑pool metrics (active threads, queue size) in production.

Common Mistakes ❌

  • Forget @EnableAsync
  • Expect async behavior on private methods
  • Block async threads with heavy logic
  • Use @Async for CPU‑heavy tasks
  • Assume transactions propagate automatically

Tips

  • Handle exceptions explicitly – async exceptions won’t propagate automatically.
  • Keep async logic lightweight@Async is not a replacement for message queues.

Conclusion 🧩

@Async in Spring Boot looks simple, but internally it relies on:

  • Spring AOP proxies
  • Task executors
  • Thread pools

Understanding these internals helps you:

  • Avoid subtle bugs
  • Write scalable applications
  • Impress interviewers 😉

Call to Action 📣

💬 Have questions about @Async or async bugs you’ve faced?

🧠 Comment below — let’s discuss!

⭐ Follow for more Java programming and Spring Boot internals content.

Back to Blog

Related posts

Read more »