Wydajność Twojej aplikacji nie zależy wyłącznie od kodu – najważniejszy jest sposób, w jaki kontener webowy zarządza żądaniami i wątkami.
Przybliżę Ci różnice między Tomcatem, Jetty i Netty – trzema popularnymi serwerami, które znacząco różnią się podejściem do obsługi ruchu.
Tomcat – klasyczny model blokujący
Tomcat to najbardziej rozpowszechniony serwer aplikacyjny w ekosystemie Java.
Domyślnie: Tomcat konfiguruje pulę 200 wątków roboczych (maxThreads=200). Każde przychodzące żądanie HTTP przypisuje do wolnego wątku.
Parametry zarządzania wątkami:
- maxThreads – maksymalna liczba wątków, które mogą równocześnie obsługiwać przychodzące żądania HTTP. Gdy liczba żądań przekracza tę wartość, kolejne trafiają do kolejki oczekujących.
Domyślnie: 200. - minSpareThreads – minimalna liczba wątków utrzymywanych w stanie gotowości, choćby gdy są bezczynne. Dzięki temu serwer może gwałtownie zareagować na nowe żądania bez konieczności tworzenia wątków „na żądanie”. Pozostałe wątki pozostają w puli w stanie bezczynności (aby nie zużywać niepotrzebnie zasobów), gotowe do ponownego użycia.
Domyślnie: 10. - acceptCount – maksymalna liczba żądań, które mogą oczekiwać w kolejce, gdy wszystkie maxThreads są zajęte. jeżeli limit ten zostanie przekroczony (np. 200 aktywnych wątków + 100 w kolejce), każde kolejne żądanie (np. 301.) zostaje odrzucone, co skutkuje błędem lub timeoutem.
Domyślnie: 100.
Jak działa request flow:
- Klient wysyła request (np. GET /users/1).
- Gniazdo (socket) akceptowane jest przez AcceptorThread.
- Trafia do Executor, który pobiera wątek z puli.
- Wątek obsługuje request: np. odczytuje dane z DB, renderuje widok.
- Odpowiedź wysyłana, wątek wraca do puli.

jeżeli wszystkie 200 wątków są zajęte – kolejne requesty czekają w kolejce (acceptCount=100), a potem… są odrzucane.
Jetty – lekki i elastyczny serwer
Jetty również operuje na modelu wątków per request, ale z bardziej elastyczną konfiguracją i mniejszym narzutem. Jest częściej używany tam, gdzie liczy się niska latencja i możliwość dostosowania.
Jetty pozwala też na konfigurację hybrydową – częściowo nieblokującą, dzięki adnotacji @WebServlet(asyncSupported = true) oraz metodzie startAsync().
Nie jest to jednak pełnoprawny reactive stack – przez cały czas sporo zależy od architektury Twojej aplikacji.
Jak działa request flow w Jetty (blokująco):
- Request trafia do QueuedThreadPool.
- Wątek obsługuje całość, analogicznie do Tomcat.
- Możliwość manualnego „zawieszenia” przetwarzania i zwolnienia wątku (startAsync()).
Z asynchronicznym API:
- Wątek może wrócić do puli zanim response zostanie wygenerowany.
- Odpowiedź może być przekazana np. przez callback lub event.
Jetty lepiej znosi bursty i scenariusze WebSocketowe, choć przez cały czas to nie w pełni reaktywny model.
Netty – w pełni asynchroniczny, event-driven
Netty działa na zupełnie innym poziomie. Netty nie oferuje gotowego serwera HTTP jak Tomcat czy Jetty – zamiast tego udostępnia niskopoziomowy model, który pozwala zbudować serwer HTTP z pełną kontrolą nad ruchem i zdarzeniami sieciowymi.
Netty działa w modelu Event Loop. Domyślnie ma:
- 1–2 wątki akceptujące – boss group (domyślnie 1),
- liczba rdzeni CPU × 2 wątków przetwarzających (worker group).
Jak dział request flow:
- Połączenie akceptowane przez bossGroup.
- Trafia do workerGroup, gdzie zarządzane jest przez selektory.
- Wszystko przetwarzane asynchronicznie, przez zdarzenia (channelRead, write, flush, itd.) a dane przekazywane są w postaci obiektów Netty (np. ByteBuf, FullHttpRequest).
- Callbacki reagują na zakończenie operacji (np. dostęp do bazy przez CompletableFuture lub Mono).

Jeden wątek Netty może obsłużyć tysiące połączeń, bo nie blokuje IO.
Porównanie: obsługa wątków i przetwarzanie
Model | Blokujący | Blokujący / Hybrydowy | W pełni nieblokujący |
Wątek per request | Tak | Tak (z opcją async) | Nie – Event Loop |
Domyślna pula wątków | 200 (maxThreads) | 250 (QueuedThreadPool) | 2 × liczba dostępnych rdzeni CPU + 1 bossGroup |
Skalowalność | Ograniczona pulą wątków | Średnia, ale lepsza od Tomcat | Wysoka |
Konfiguracja | Prosta | Elastyczna | Złożona, niskopoziomowa (Spring Boot zapewnia konfiguracje za nas) |
Idealny use case | Klasyczne aplikacje webowe | Lightweight REST, WebSocket | Reactive, event-driven apps |
Kiedy wybrać który?
Tomcat i Jetty to świetne rozwiązania dla klasycznych aplikacji – łatwe w konfiguracji, sprawdzone. Ale w sytuacji, gdy liczba requestów rośnie wykładniczo, wątek per request przestaje być opłacalny.
Netty oferuje zupełnie inny poziom: w pełni reaktywna obsługa, która minimalizuje zużycie zasobów. Wymaga jednak więcej wysiłku – zarówno od strony konfiguracyjnej jak i zasad implementacji rozwiązania, które na nim działa.
Tomcat – dla większości klasycznych aplikacji Spring MVC, kiedy nie przewidujesz gigantycznego ruchu.
Jetty – gdy chcesz więcej kontroli i elastyczności przy mniejszym narzucie, np. w microservices.
Netty – jeżeli tworzysz system wymagający maksymalnej wydajności i kontroli nad ruchem – API gateway, serwer HTTP/2, websockety, broker wiadomości.
Co z wirtualnymi wątkami?
Od JDK 21 wchodzą w grę wirtualne wątki (Project Loom) – przełom, który może zmienić podejście do serwerów opartych na wątkach per request. Umożliwiają one obsługę milionów requestów bez zmiany architektury na reaktywną.
Ważne – wirtualne wątki zmieniają model wykonania, ale nie rozwiązują wszystkiego – przez cały czas wymagają przemyślanej współpracy z blokującymi zasobami (np. I/O, bazy danych).
Zobacz, jak wirtualne wątki przyspieszają aplikacje – pełne szkolenie!
Sprawdź mój materiał na YouTube, w którym krok po kroku pokazuję, jak uruchomić wirtualne wątki w Spring Boot. Live coding, praktyczne przykłady i porównanie wydajności – wszystko, czego potrzebujesz, aby wynieść swoje API na wyższy poziom!