Spring MVC: запрос обрабатывает поток из пула Tomcat (по умолчанию 200). Пока код ждёт базу или внешний сервис, поток занят. Простой последовательный код и огромная экосистема (JPA, JDBC, большинство библиотек блокирующие). Предел — число потоков: при медленных зависимостях пул кончается.
Spring WebFlux: работает на Netty с несколькими потоками event loop по числу ядер. Обработчики возвращают Mono/Flux, вся цепочка неблокирующая: WebClient для HTTP, R2DBC для баз, реактивные драйверы. Плюсы — тысячи одновременных соединений при малом числе потоков, естественный стриминг и backpressure. Минусы — сложнее код, отладка, тесты; любой блокирующий вызов в цепочке (JDBC, block(), тяжёлые вычисления) замораживает event loop и все соединения на нём.
Виртуальные потоки (Java 21, Spring Boot 3.2+): spring.threads.virtual.enabled=true — Tomcat обрабатывает каждый запрос в виртуальном потоке. Код остаётся простым и блокирующим, а ожидание не держит поток ОС. Для многих сервисов это снимает главный аргумент в пользу WebFlux.
Выбор: обычный сервис с базой через JPA — MVC (с виртуальными потоками при большом числе ожиданий); API-шлюз, стриминг, SSE/WebSocket, агрегирование многих внешних вызовов — WebFlux; смешивать оба стека в одном приложении не стоит.