@Async in Spring Boot 4: Executors, Virtual Threads and the Self-Invocation Trap
@Async is a proxy, and every surprising thing it does follows from that. Measured on Spring Boot 4.1.1 and JDK 25: the self-invocation trap, the IllegalArgumentException a plain return type throws, why max-size does nothing until queue-capacity is bounded, two Executor beans leaving @Async on an unpooled SimpleAsyncTaskExecutor, the new spring.task.execution.propagate-context property, and JEP 491 measured at 4806 ms on JDK 21 against 301 ms on JDK 25.