Tomcat vs Jetty vs Undertow: Which Spring Boot Embedded Server Performs Best?
This article builds a simple Spring Boot Greetings API, configures Maven profiles for Tomcat, Jetty, and Undertow, creates Docker images for each, runs load‑testing benchmarks across multiple concurrency levels, and analyzes startup time, CPU, memory, and response performance to determine the optimal embedded server.
Test Application
We create a simple Spring Boot Greetings API that exposes /greetings?name=?. If name is omitted, it returns "[greeting‑word] World!"; otherwise it returns "[greeting‑word] [name]!".
Implementation
@Service
public class GreetingsService {
private static final String[] greetingWord = new String[]{"Hello", "Hi", "Olá", "Oi", "Hallo"};
public String randomGreetings(String name) {
try {
String word = greetingWord[ThreadLocalRandom.current().nextInt(greetingWord.length)];
Thread.sleep(1000);
return "%s %s!".formatted(word, name);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
@RestController
@RequestMapping("/greetings")
public class GreetingsController {
private final GreetingsService greetingsService;
public GreetingsController(GreetingsService greetingsService) {
this.greetingsService = greetingsService;
}
@GetMapping
public String greeting(@RequestParam(required = false, defaultValue = "World") String name) {
return greetingsService.randomGreetings(name);
}
}Jetty and Undertow Configuration
We add Maven profiles to pom.xml to switch between embedded servers.
<profiles>
<!-- Jetty Profile -->
<profile>
<id>jetty</id>
<activation><activeByDefault>false</activeByDefault></activation>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
</dependencies>
</profile>
<!-- Undertow Profile -->
<profile>
<id>undertow</id>
<activation><activeByDefault>false</activeByDefault></activation>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>application.properties Configuration
Thread settings for each server (max 200 threads):
server.tomcat.threads.max=200
server.jetty.threads.max=200
server.undertow.threads.worker=200Build Docker Images
# Tomcat image
./mvnw clean -DskipTests spring-boot:build-image -Dspring-boot.build-image.imageName=greetings-api-tomcat:latest
# Jetty image
./mvnw clean -DskipTests spring-boot:build-image -Pjetty -Dspring-boot.build-image.imageName=greetings-api-jetty:latest
# Undertow image
./mvnw clean -DskipTests spring-boot:build-image -Pundertow -Dspring-boot.build-image.imageName=greetings-api-undertow:latestBenchmark
Using the open‑source api‑oha‑benchmarker , each Docker container is started, subjected to 100, 300, 900, and 2700 concurrent requests, then stopped.
Results (Requests/sec)
Application | numRequests | Concurrency | Success rate(%) | Total(secs) | Slowest(secs) | Fastest(secs) | Average(secs) | Requests/sec
------------+------------+-------------+----------------+------------+---------------+---------------+---------------+------------
greetings-api-tomcat | 100 | 100 | 100.00 | 1.2949 | 1.2942 | 1.2701 | 1.2822 | 77.2256
greetings-api-tomcat | 300 | 300 | 100.00 | 2.1069 | 2.0835 | 1.0311 | 1.4158 | 142.3913
greetings-api-tomcat | 900 | 900 | 100.00 | 5.1386 | 5.0797 | 1.0283 | 2.8856 | 175.1446
greetings-api-tomcat | 2700 | 2700 | 100.00 | 14.3752 | 14.2423 | 1.0296 | 7.4867 | 187.8241
greetings-api-jetty | 100 | 100 | 100.00 | 2.0717 | 2.0708 | 1.2639 | 1.5895 | 48.2692
greetings-api-jetty | 300 | 300 | 100.00 | 3.1081 | 3.1056 | 1.0307 | 1.7496 | 96.5223
greetings-api-jetty | 900 | 900 | 100.00 | 5.1943 | 5.1762 | 1.0381 | 3.0216 | 173.2659
greetings-api-jetty | 2700 | 2700 | 100.00 | 15.3022 | 15.2278 | 1.0160 | 7.9127 | 176.4455
greetings-api-undertow| 100 | 100 | 100.00 | 1.3076 | 1.3066 | 1.2665 | 1.2833 | 76.4765
greetings-api-undertow| 300 | 300 | 100.00 | 2.0819 | 2.0776 | 1.0413 | 1.4058 | 144.0982
greetings-api-undertow| 900 | 900 | 100.00 | 5.1443 | 5.1121 | 1.0780 | 2.8783 | 174.9493
greetings-api-undertow| 2700 | 2700 | 100.00 | 14.1748 | 14.0861 | 1.0758 | 7.3721 | 190.4783Startup Time, CPU, Memory
Application | StartUpTime(sec) | Max CPU(%) | Max Memory(MB)
------------+-----------------+-----------+----------------
greetings-api-tomcat | 1.8920 | 101.26 | 269.10
greetings-api-jetty | 1.9790 | 100.40 | 217.70
greetings-api-undertow| 2.0450 | 124.15 | 237.50Response Time
For 100 concurrent requests, Tomcat is fastest (1.2949 s), followed by Undertow (1.3076 s); Jetty is slowest (2.0717 s). At 300 requests, Undertow leads (2.0819 s), then Tomcat (2.1069 s), Jetty last (3.1081 s). At 900 requests, Tomcat is slightly faster (5.1386 s) than Undertow (5.1443 s) and Jetty (5.1943 s). At 2700 requests, Undertow is fastest (14.1748 s), Tomcat second (14.3752 s), Jetty last (15.3022 s).
Startup Time
Tomcat starts fastest (1.892 s), Jetty follows (1.979 s), and Undertow is slowest (2.045 s).
Memory Usage
Jetty consumes the least memory (217.70 MB), Undertow next (237.50 MB), and Tomcat the most (269.10 MB).
CPU Usage
Jetty shows the lowest peak CPU (100.40 %), Tomcat slightly higher (101.26 %), while Undertow peaks at 124.15 %.
Conclusion
Tomcat offers the fastest startup and solid performance across all traffic levels but uses more memory. Undertow excels under high concurrency with the best throughput, at the cost of higher CPU usage and slower startup. Jetty provides the lowest memory and CPU footprints, though its response times are slower, making it a good choice when resource conservation is paramount.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
