Quarkus Native vs JVM: Real-World Performance Comparison

Written by Issam on November 17, 2025 • 10 min read

backendapp

Quarkus Logo

Discover the real-world performance differences between Quarkus Native Image and Quarkus JVM. Compare build times, Docker image sizes, startup times, and memory usage with actual measurements from a production-ready application. Make informed decisions about which deployment option suits your use case.


Introduction

Quarkus offers two primary deployment options: Native Image (compiled ahead-of-time with GraalVM) and Traditional JVM (running on the Java Virtual Machine). Both have their strengths, but choosing the right one depends on your specific requirements.

This article presents a comprehensive, real-world comparison using the same Quarkus application built and deployed in both modes. We’ll measure:

Methodology

All measurements were taken using:

The application includes:

Build Time Comparison

Native Image Build

Building a Quarkus Native Image involves ahead-of-time compilation, which is computationally intensive:

cd quarkus
./mvnw -Pnative -Dquarkus.native.container-build=true clean package

Results:

JVM Build

Building for JVM is a standard Java compilation process:

cd quarkus
./mvnw -Pjvm clean package

Results:

Build Time Analysis

MetricNative ImageJVMDifference
Build Time~210 seconds~15 seconds195 seconds (1300% slower)
Build ComplexityHigh (AOT compilation)Low (Standard compilation)-

Key Observations:

Docker Image Size Comparison

Native Image Dockerfile

FROM quay.io/quarkus/quarkus-micro-image:2.0
WORKDIR /work/
COPY target/*-runner /work/application/application
EXPOSE 8080
ENTRYPOINT ["/work/application/application"]

Results:

JVM Dockerfile

FROM eclipse-temurin:21-jre-alpine
WORKDIR /work/
COPY target/quarkus-app /work/application
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/work/application/quarkus-run.jar"]

Results:

Image Size Analysis

MetricNative ImageJVMDifference
Docker Image Size123 MB254 MB131 MB (107% larger for JVM)
Base ImageMinimal UBIAlpine + JRE-
Application ArtifactNative executableJAR + dependencies-

Key Observations:

Startup Time Comparison

Native Image Startup

Native executables start almost instantly because:

Results:

Actual Log Output:

quarkus-app  | 2025-11-16 23:09:36,900 INFO  [io.quarkus] (main) quarkus-native-users 1.0.0-SNAPSHOT native (powered by Quarkus 3.15.1) started in 0.079s. Listening on: http://0.0.0.0:8080

JVM Startup

JVM applications take longer to start because:

Results:

Actual Log Output:

quarkus-jvm-app | 2025-11-16 23:11:45,553 INFO  [io.quarkus] (main) quarkus-native-users 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.15.1) started in 2.292s. Listening on: http://0.0.0.0:8080

Note: These startup times are measured by Quarkus itself and represent the actual application startup time (from application initialization to ready state), which is more accurate than external measurements that include container startup overhead.

Startup Time Analysis

MetricNative ImageJVMDifference
Startup Time0.079 s (79 ms)2.292 s2.213 s (~29x faster for Native)
Cold StartInstantRequires warmup-
First RequestFastSlower (JIT not warmed up)-

Key Observations:

Memory Usage Comparison

Memory at Startup

Memory usage immediately after application startup:

Native Image:

JVM:

Real Docker Stats Output (JVM at Startup):

CONTAINER ID   NAME               MEM USAGE / LIMIT     MEM %
40292f4fa0a6   quarkus-jvm-app    113.5MiB / 128MiB     88.69%

Real Docker Stats Output (Native at Startup):

CONTAINER ID   NAME               MEM USAGE / LIMIT     MEM %
435d098f0299   quarkus-app        16.27MiB / 64MiB      25.43%

Memory Under Load

Memory usage after handling API requests:

Native Image:

Real Docker Stats Output (Native Under Load):

CONTAINER ID   NAME               MEM USAGE / LIMIT     MEM %
435d098f0299   quarkus-app        26.71MiB / 64MiB      41.74%

JVM:

Real Docker Stats Output (JVM Under Load):

CONTAINER ID   NAME               MEM USAGE / LIMIT     MEM %
40292f4fa0a6   quarkus-jvm-app    122.6MiB / 128MiB    95.76%

Memory Analysis

MetricNative ImageJVMDifference
Memory at Startup16.27 MiB113.5 MiB97.23 MiB (~86% less for Native)
Memory under Load26.71 MiB122.6 MiB95.89 MiB (~78% less for Native)
Memory Growth~10.44 MiB~9.1 MiB-

Key Observations:

Build Artifact Size

Native Executable

The compiled native executable:

JVM JAR

The packaged JAR file:

Artifact Size Analysis

MetricNative ExecutableJVM JARNotes
Size~99 MB~49 MBNative executable is larger but includes everything
PortabilityPlatform-specificCross-platformJAR runs on any JVM

Performance Summary

Quick Comparison Table

MetricNative ImageJVMWinner
Build Time~210s (3.5 min)~15sJVM (14x faster)
Docker Image Size123 MB254 MBNative (52% smaller)
Startup Time0.079 s (79 ms)2.292 sNative (~29x faster)
Memory (Startup)16.27 MiB113.5 MiBNative (86% less)
Memory (Load)26.71 MiB122.6 MiBNative (78% less)
Runtime PerformanceGoodExcellent (after warmup)JVM (long-term)

When to Use Native Image

Choose Native Image when:

Fast startup is critical

Memory is constrained

Cold start performance matters

Smaller deployments

When to Use JVM

Choose JVM when:

Build time is a concern

Maximum runtime performance needed

Easier debugging and profiling

Complex reflection/dynamic code

Configuration Examples

Building Native Image

cd quarkus
./mvnw -Pnative -Dquarkus.native.container-build=true \
  -Dquarkus.container-image.build=true \
  -Dquarkus.container-image.name=quarkus-native-users \
  -Dquarkus.container-image.tag=latest \
  clean package

Building JVM Image

cd quarkus
./mvnw -Pjvm -Dquarkus.container-image.build=true \
  -Dquarkus.container-image.name=quarkus-jvm-users \
  -Dquarkus.container-image.tag=latest \
  clean package

Docker Compose

Native:

docker-compose up -d

JVM:

docker-compose -f docker-compose.jvm.yml up -d

Real-World Use Cases

Serverless Functions

Native Image is ideal for serverless:

Microservices

Both can work, but consider:

Batch Processing

Native Image excels for:

Traditional Web Applications

JVM is often better for:

Conclusion

Both Quarkus Native Image and JVM have their place in modern Java development:

The choice depends on your specific requirements:

Key Takeaways

  1. Native Image offers ~29x faster startup (0.079s vs 2.292s) and 86% less memory at startup (16.27 MiB vs 113.5 MiB)
  2. JVM offers 14x faster builds (~15s vs ~210s) and better long-term performance after warmup
  3. Native Image produces 52% smaller Docker images (123MB vs 254MB)
  4. Both are production-ready and well-supported by Quarkus
  5. Measure your specific use case to make the best decision

Resources


Follow me on GitHub and connect with ForTek Advisor for more technical content and project updates.

Originally published at https://fortek-advisor.com on November 17, 2025.