Spring Boot @Async – Part 2

 


Spring Boot @Async – Part 2


1) Conditions for @Async to Work (Page 1)

From PDF

  1. Different Class

If @Async is called from the same class, the Spring proxy is bypassed
(internal method calls are not intercepted).

  1. Public Method

The method annotated with @Async must 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).


  •  

Leave a Reply