Implementing Asynchronous Processing

1. Enabling Async Support (@EnableAsync)

Example: Enable async execution

@SpringBootApplication
@EnableAsync
public class App {}
Attribute Description
proxyTargetClass Force CGLIB proxies
mode PROXY (default) or ASPECTJ
annotation Custom marker annotation

2. Creating Async Methods (@Async)

Example: Async email with CompletableFuture

@Service
public class NotifyService {
  @Async
  public void sendEmail(String to, String body) { /* slow */ }

  @Async("emailExecutor")
  public CompletableFuture<String> sendAndReturn(String to) {
    return CompletableFuture.completedFuture("sent:" + to);
  }
}

3. Returning Future and CompletableFuture

Return Type Behavior
void Fire-and-forget
Future<T> Blocking get()
CompletableFuture<T> Composable, recommended
ListenableFuture<T> DEPRECATED Use CompletableFuture

4. Configuring Thread Pool Executor (TaskExecutor)

Example: Thread pool executor for email tasks

@Bean("emailExecutor")
ThreadPoolTaskExecutor emailExecutor() {
  ThreadPoolTaskExecutor ex = new ThreadPoolTaskExecutor();
  ex.setCorePoolSize(4);
  ex.setMaxPoolSize(16);
  ex.setQueueCapacity(500);
  ex.setThreadNamePrefix("email-");
  ex.setRejectedExecutionHandler(new CallerRunsPolicy());
  ex.initialize();
  return ex;
}

Example: Async task pool configuration

spring:
  task:
    execution:
      pool: { core-size: 4, max-size: 16, queue-capacity: 500 }
      thread-name-prefix: app-task-

5. Handling Async Exceptions

Example: Handle uncaught async exceptions

@Configuration
public class AsyncConfig implements AsyncConfigurer {
  @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return (ex, method, params) ->
      LoggerFactory.getLogger(method.getDeclaringClass())
        .error("Async error in {}", method.getName(), ex);
  }
}
Return Exception Handling
void AsyncUncaughtExceptionHandler
Future Wrapped in ExecutionException on get()

6. Using @Async with Transactions

Warning: @Async runs on a different thread → transaction does NOT propagate. Annotate the async method itself with @Transactional.
Pattern Effect
Caller @Transactional + callee @Async Async runs WITHOUT tx
Async method @Transactional Starts new tx on async thread

7. Implementing Async REST Endpoints

Example: DeferredResult and Callable endpoint

@GetMapping("/slow")
public DeferredResult<String> slow() {
  DeferredResult<String> result = new DeferredResult<>(5000L, "timeout");
  CompletableFuture.supplyAsync(this::compute).thenAccept(result::setResult);
  return result;
}

@GetMapping("/cb")
public Callable<String> viaCallable() { return () -> compute(); }

8. Using WebAsyncTask for Timeouts

Example: Async task with per-request timeout

@GetMapping("/report")
public WebAsyncTask<Report> report() {
  return new WebAsyncTask<>(3000L, "reportExecutor", () -> service.build());
}

9. Configuring Async Request Processing

Property Use
spring.mvc.async.request-timeout Default async timeout (ms)
WebMvcConfigurer.configureAsyncSupport Programmatic config

10. Using ListenableFuture for Callbacks

Note: ListenableFuture is deprecated; use CompletableFuture with thenApply, exceptionally, etc.
CompletableFuture API Use
thenApply(fn) Transform result
thenCompose(fn) Chain async
thenCombine(other,fn) Combine two futures
exceptionally(fn) Recover from error
orTimeout(d,unit) Fail after timeout

11. Testing Async Methods

Example: Test async method with Awaitility

@SpringBootTest
class NotifyTests {
  @Autowired NotifyService svc;
  @Test void sends() throws Exception {
    CompletableFuture<String> f = svc.sendAndReturn("a@b");
    Awaitility.await().atMost(Duration.ofSeconds(2)).until(f::isDone);
    assertEquals("sent:a@b", f.get());
  }
}