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.

Programmer DD
Programmer DD
Programmer DD
Tomcat vs Jetty vs Undertow: Which Spring Boot Embedded Server Performs Best?

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=200

Build 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:latest

Benchmark

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.4783

Startup 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.50

Response Time

Response time chart
Response time chart

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

Startup time chart
Startup time chart

Tomcat starts fastest (1.892 s), Jetty follows (1.979 s), and Undertow is slowest (2.045 s).

Memory Usage

Memory usage chart
Memory usage chart

Jetty consumes the least memory (217.70 MB), Undertow next (237.50 MB), and Tomcat the most (269.10 MB).

CPU Usage

CPU usage chart
CPU usage chart

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DockerSpring BootbenchmarktomcatUndertowJettyembedded server
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.