Chapter 6.4☕ 14 min read

Async Processing

Take a token, sit down. The kitchen cooks your dosa in the background.

01The Concept: Non-Blocking Tasks

The Hyderabad Tiffin Center Token System Analogy:

At a busy Tiffin center in Ameerpet, you don’t stand at the counter for 15 minutes while they cook your dosa. You pay, take a token, and go sit down. The kitchen prepares the dosa asynchronously in the background. When it’s ready, they call your token number.

In Spring Boot, @Async is the token system. When a user calls an API, Spring Boot takes the request, hands it to a background thread pool, and immediately returns a “200 OK” to the user. The background thread does the heavy lifting.

02Technical Explanation
  1. @EnableAsync: Placed on the main class to enable async execution.
  2. @Async: Placed on a method. Spring intercepts the call and runs it on a separate background thread.
  3. CompletableFuture<T>: The modern way to return the result of an async task. The user can check if it’s done and retrieve the result later.
03Full Working Code: Background Data Processing

First, enable Async in your main class.

package com.devinhyderabad;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync // 1. Turn on the async engine
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
04Async Service and Controller
// Async Service
package com.devinhyderabad;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class ReportService {

// 2. @Async sends this to a background thread
@Async
public CompletableFuture<String> generateHeavyReport() {
try {
System.out.println("Generating report on: " + Thread.currentThread().getName());
Thread.sleep(5000); // Simulate a 5-second heavy task
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return CompletableFuture.completedFuture("Report Generated Successfully!");
}
}

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

@Autowired
private ReportService reportService;

// Returns IMMEDIATELY, does NOT wait 5 seconds
@GetMapping("/generate")
public String generateReport() {
reportService.generateHeavyReport();
return "Report generation started in the background!";
}

// Returns the actual result after 5 seconds
@GetMapping("/generate-and-wait")
public CompletableFuture<String> generateAndWait() {
return reportService.generateHeavyReport();
}
}
05Why It Matters / Interview Note

Interview Question: “Does @Async work if you call the method from within the same class?”

Answer: No. @Async relies on Spring’s AOP proxy. If class A calls a @Async method inside class A, it bypasses the proxy and runs synchronously. The @Async method must be called from a different bean (e.g., Controller calling Service).

Enterprise Note: By default, Spring uses SimpleAsyncTaskExecutor which creates a new thread for every task (bad for performance). In production, you must define a custom Executor bean with a thread pool to limit the number of background threads and prevent system crashes.

Key Takeaways

  • ✅ @EnableAsync activates async execution in Spring Boot
  • ✅ @Async runs a method on a background thread, returning immediately
  • ✅ CompletableFuture allows retrieving the result of an async task
  • ✅ @Async only works when called from a different bean (AOP proxy requirement)
  • ✅ Always define a custom Executor bean with thread pool in production