Choosing Between Quarkus Native Executable and JVM Mode
A decision guide that outlines constraints, compares native and JVM options, explains trade‑offs, and shows how to build and verify a native executable.
12 Jul 2026, 00:13 UTC

Decision and Constraints
When developing a Quarkus application you must decide whether to produce a native executable with GraalVM or to run the application on the HotSpot JVM. The choice hinges on constraints such as startup latency, memory budget, build time, library compatibility, and development workflow.
- Startup latency – critical for serverless functions or short‑lived containers.
- Memory footprint** – important when running many instances on limited hardware.
- Build time** – affects CI/CD pipelines and local iteration speed.
- Library support** – some frameworks rely on reflection or dynamic class loading that may need extra configuration for native images.
- Development mode** – hot reload and fast debugging are only available in JVM mode.
If your target environment demands sub‑100 ms start and low RSS (e.g., AWS Lambda, Knative, or dense microservice clusters), native compilation is attractive. If you prioritize rapid iteration, full library compatibility, or easier debugging, the JVM mode is preferable.
Comparison of Options
| Aspect | Native Executable (GraalVM) | JVM (HotSpot) |
|---|---|---|
| Startup time | <100 ms (typical) |
1‑2 s |
| Memory RSS | ~30 MB |
~150 MB |
| Build time | 2‑5 min (depends on project size) |
<30 s |
| Library support | Limited; requires reflection configuration for dynamic loading | Full; no extra configuration needed |
| Dev mode | Not available | Enabled via quarkus:dev |
| Typical use case | Serverless functions, CLI tools, low‑latency microservices | General purpose apps, APIs needing fast iteration |
Trade‑offs
Choosing native execution trades build speed and library flexibility for instantaneous start and low memory consumption. The native image builder must analyze all reachable classes; any reflection‑based class loading not declared in the configuration leads to a ClassNotFoundException at runtime. Consequently, you may need to add entries to quarkus.native.additional-build-args or create a reflect-config.json file.
Running on the JVM preserves full dynamic language features, allows hot reload in dev mode, and yields faster builds, but the higher startup time and memory footprint can increase costs in autoscaling environments and affect cold‑start latency.
Implementation: Building and Verifying a Native Executable
Prerequisites
- GraalVM 22+ installed and
JAVA_HOMEpointing to it. - Quarkus project with the
quarkus-native-imageextension added. - Access to run Maven (
./mvnw) with sufficient disk space for the native image build.
Add the Native Image Extension
If not already present, add the extension:
./mvnw quarkus:add-extension -Dextensions="quarkus-native-image"
Configure Reflection (if needed)
Suppose your application uses Jackson to deserialize polymorphic types. You must register the relevant classes for reflection. Add the following to src/main/resources/application.properties:
quarkus.native.additional-build-args=--initialize-at-run-time=com.example.model.PolymorphicType
Alternatively, create a reflect-config.json file in src/main/resources/META-INF/native-image with the appropriate name and allDeclaredConstructors, allDeclaredMethods flags.
Build the Native Executable
Run the native profile:
./mvnw package -Pnative
This command invokes GraalVM’s native-image tool. Expect the build to take several minutes. Successful completion produces a binary in target/<artifactId>-<version>-runner (or <artifactId>-<version>-runner.exe on Windows).
Verify Startup Time and Memory Usage
Execute the binary and measure startup with the time shell builtin (or /usr/bin/time for more detail). Example:
time ./target/my-app-1.0.0-runner
Check the resident set size after the process starts (in another terminal):
ps -o rss -p <pid>
You should observe a startup time well under 100 ms and an RSS around 30 MB for a typical hello‑world service. If the process fails with a ClassNotFoundException, revisit the reflection configuration and add the missing classes.
Validate JVM Mode for Comparison
To see the contrast, run the same code in JVM mode:
./mvnw quarkus:devor
java -jar target/*-runner.jarMeasure startup with
timeand RSS withps; you will typically see 1‑2 seconds start and ~150 MB RSS.Limitations and Practical Checks
- Version compatibility – Native image builds are sensitive to the exact GraalVM and Quarkus versions. Consult the Quarkus compatibility matrix; mismatched versions can cause build errors or runtime crashes.
- Reflection overhead** – Excessive reflection increases native image size and may necessitate extensive configuration. Use the
--report-unsupported-elements-at-runtimeflag during a trial build to detect missing metadata. - Operating system differences** – A native image built on Linux cannot run on macOS or Windows without rebuilding for that target.
- Checking the result** – Beyond startup time and RSS, verify that core functionality works by exercising a representative request (e.g., HTTP endpoint) and confirming correct responses.
By following the steps above you can make an informed decision, produce a native executable when appropriate, and validate its behavior against the JVM baseline.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.