> on modern processors a couple thousand mostly idle (due to IO blocking) Java threads are fine
This “mostly idle” assumption is gonna break the moment you hit a load spike and wake up most of those “idle” threads.
The success of async is often (mis)attributed to eliminating unnecessary resources, but in practice it's actually because async model implicitly enforces an invisible job queue, where usually only a single process/thread per core is heavy lifting the actual work, and async I/O will feed processing pressure back to the load source via network (e.g. TCP ACK).