Spring Boot @Async – Part 2
1) Conditions for @Async to Work (Page 1)
From PDF
- Different Class
If
@Asyncis called from the same class, the Spring proxy is bypassed
(internal method calls are not intercepted).
- Public Method
The method annotated with
@Asyncmust be public.
❌ Wrong:
@Component
public class UserService {
@Async
void asyncMethod() { } // NOT public
public void mainMethod() {
asyncMethod(); // same class → proxy skipped
}
}
✔ Correct:
@Component
public class UserService {
@Async
public void asyncMethod() { }
}
2) @Async and Transaction Management (Page 1)
UseCase 1 ❌
Transaction context does NOT move from parent thread to async thread.
@Transactional
public void updateUser() {
userUtility.updateBalanceAsync(); // @Async
}
➡ Async thread runs without parent transaction context.
UseCase 2 ⚠
@Transactional
@Async
public void updateUser() { }
- New thread
- New transaction
- Propagation does not behave as expected
UseCase 3 ✔ (Recommended) (Page 1)
Controller → Service → Utility
@Service
public class UserService {
@Autowired UserUtility utility;
@Async
public void updateUser() {
utility.updateUserTx();
}
}
@Component
public class UserUtility {
@Transactional
public void updateUserTx() { }
}
➡ Async + Transaction works correctly.
3) Async Method Return Types (Page 2)
From PDF:
Async methods can return Future or CompletableFuture.
Using Future
@Async
public Future<String> performTaskAsync() {
return new AsyncResult<>("Task Result");
}
Controller:
Future<String> result = service.performTaskAsync();
System.out.println(result.get()); // blocks
Future API (Page 2 table)
| Method | Purpose |
|---|---|
| cancel() | Cancels task |
| isCancelled() | Check if cancelled |
| isDone() | Completed or not |
| get() | Wait & get result |
| get(timeout) | Wait for fixed time |
4) Using CompletableFuture (Page 2)
Introduced in Java 8.
@Async
public CompletableFuture<String> performAsync() {
return CompletableFuture.completedFuture("Async result");
}
Why better?
- Non-blocking
- Supports chaining
- Better exception handling
5) Exception Handling in @Async (Page 3)
Case 1: Async method with return type
@Async
public Future<String> task() {
throw new RuntimeException();
}
➡ Caller can catch using future.get().
Case 2: Async method with void return
@Async
public void task() {
int x = 10 / 0; // exception
}
➡ Cannot catch in caller.
6) How to Handle Void Async Exceptions (Page 3)
Option 1: Try–Catch inside async method
@Async
public void task() {
try {
int x = 10 / 0;
} catch(Exception e) {
// handle
}
}
Option 2: Custom AsyncUncaughtExceptionHandler
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
}
public class CustomAsyncExceptionHandler
implements AsyncUncaughtExceptionHandler {
public void handleUncaughtException(Throwable ex, Method m, Object... params) {
System.out.println("Async error: " + ex.getMessage());
}
}
Default Behavior (Page 3–4)
If not handled, Spring uses:
SimpleAsyncUncaughtExceptionHandler
🎯 Interview Q&A
Q1. Why @Async doesn’t work in same class?
Because internal calls bypass Spring proxy.
Q2. Can @Async work on private method?
No. It must be public.
Q3. Does @Async share transaction context?
No. New thread = new context.
Q4. Best way to combine @Async + @Transactional?
Async in Service → Transaction in Utility/Repo.
Q5. What return types are supported?
void, Future<T>, CompletableFuture<T>.
Q6. How to handle async exceptions?
- Return type →
get() - Void →
AsyncUncaughtExceptionHandler
Q7. Which is better: Future or CompletableFuture?
CompletableFuture (non-blocking, chaining, better error handling).
