framework-benchmarks

Three identical REST APIs - Spring Boot, Quarkus, Micronaut - built JVM and native, and benchmarked for cold-start time, resident memory, and throughput. Companion repo for the ankurm.com post "Spring Boot 4.1 vs Quarkus vs Micronaut: Startup, Memory and Throughput Benchmarked."

No docs/ chapter folder here by design - every finding below is written up in full in the post itself, inside per-section <details> accordions. This repo carries the runnable code and the raw captured output those sections link to.

Versions under test

Component Version Verified against
Spring Boot 4.1.1 spring-boot-dependencies Maven Central metadata
Quarkus 3.40.1 (the 3.40 LTS stream) quarkus-bom Maven Central metadata + quarkus.io/releases
Micronaut 5.2.2 micronaut-platform Maven Central metadata
GraalVM CE 25.4.4.1.1 (OpenJDK 25.0.4.1.1) graalvm/graalvm-ce-builds GitHub releases (latest at build time)
JDK baseline 25 (current LTS) all three apps compiled and run on the same GraalVM JDK 25

Quickstart

# JVM mode
cd spring-boot-app && mvn -q package -DskipTests && java -jar target/spring-boot-app.jar   # :8081
cd quarkus-app     && mvn -q package -DskipTests && java -jar target/quarkus-app/quarkus-run.jar  # :8082
cd micronaut-app   && mvn -q package -DskipTests && java -jar target/micronaut-app.jar     # :8083

# Native mode (needs GraalVM's native-image on PATH; budget ~7-8 min per app)
cd spring-boot-app && mvn -q -Pnative package -DskipTests && mvn -q -Pnative native:compile && ./target/spring-boot-app
cd quarkus-app     && mvn -q -Pnative clean package -DskipTests && ./target/quarkus-app-runner
cd micronaut-app   && mvn -q -Pnative clean package -DskipTests \
                        && mvn -q -Pnative package org.graalvm.buildtools:native-maven-plugin:compile -DskipTests \
                        && ./target/micronaut-app

scripts/run-all.sh does all of the above plus every benchmark, end to end, and regenerates every file in docs/output/.

Modules

Module Framework JVM port Build profile for native
spring-boot-app Spring Boot 4.1.1, Tomcat, Spring MVC 8081 -Pnative, then explicit native:compile (see below)
quarkus-app Quarkus 3.40.1, Quarkus REST (RESTEasy Reactive), Vert.x 8082 -Pnative
micronaut-app Micronaut 5.2.2, Netty, Micronaut Serialization (compile-time, not reflection) 8083 -Pnative + explicit native-maven-plugin:compile (see below)

All three expose the identical endpoints, same JSON shapes, same seed data, no database:

Method Path Behaviour
GET /widgets list all widgets (seeded with 5)
GET /widgets/{id} one widget, 404 if unknown
POST /widgets create ({"name": "...", "quantity": N}), 400 on blank name or negative quantity
GET /health (Micronaut) / /actuator/health (Spring Boot) / /q/health (Quarkus) trivial liveness check - deliberately each framework's own normal way to add one, not forced to be identical, since that difference is itself one of the post's findings

No database on purpose: it isolates framework/runtime startup overhead from JDBC driver and native-image reflection-metadata concerns, which is a separate, larger topic than this post covers.

The two real build failures this repo hit (and the fixes, committed as-is)

  1. Micronaut silently ignored application.yml. micronaut-yaml (which wraps snakeyaml) isn't a transitive dependency of micronaut-http-server-netty - without it, Micronaut skips YAML config files with no warning and falls back to every default (here, port 8080 instead of the configured 8083). Fixed by using application.properties instead, matching the other two modules. See the post's Micronaut section for the full story.

  2. Micronaut's native-image build failed four times in a row on a Netty/logback static-initialization clash before --initialize-at-run-time=io.netty (the whole package) fixed it. All four real native-image error messages are in docs/output/06-micronaut-native-build-failures.txt, verbatim. The working build argument lives in micronaut-app/pom.xml's native profile.

Also note: neither native-maven-plugin nor Spring Boot's own native profile auto-binds native-image to a Maven phase in a plain multi-module layout the way mvn -Pnative package alone implies - both modules need the goal invoked explicitly (native:compile, or Micronaut's native-maven-plugin:compile). See scripts/run-all.sh for the exact commands that actually trigger a native build end to end.

Captured output

File What it is
docs/output/01-startup-memory-jvm.txt All 3 frameworks, JVM mode, 4 cold-start runs each, real measure.py JSON
docs/output/02-throughput-jvm.txt All 3 frameworks, JVM mode, 15s/20-concurrency throughput runs
docs/output/03-startup-memory-native.txt Same as 01, native mode
docs/output/04-throughput-native.txt Same as 02, native mode
docs/output/05-summary.txt Consolidated table, hand-written from the four files above
docs/output/06-micronaut-native-build-failures.txt The four real native-image build attempts for Micronaut, verbatim

scripts/measure.py and scripts/throughput.py are dependency-free Python (stdlib only) - no wrk/hey/ab install required to reproduce any number in this repo.

License

MIT

S
Description
Spring Boot 4.1.1 vs Quarkus 3.40.1 vs Micronaut 5.2.2 - identical REST APIs, JVM and native, benchmarked for startup, memory and throughput. Companion repo for the ankurm.com post.
Readme MIT
48 KiB
Languages
Java 48.8%
Python 32.5%
Shell 18.7%