A detailed analysis comparing Spring Boot on JVM versus Quarkus native compilation across different concurrency levels, revealing how runtime strategies impact resource efficiency, scalability, and operational resilience.
JVM vs Native: Performance Trade-offs in Spring Boot and Quarkus
In financial systems with aggressive cost optimization strategies, operational constraints often demand more than just raw performance. When deploying on spot instances that can be terminated at any time, three factors become critical: fast startup, resource efficiency, and predictable performance under constrained CPU and memory conditions.
While exploring alternatives within the Java ecosystem for these environments, I conducted a comprehensive comparison between the traditional JVM approach using Spring Boot and the native compilation approach using Quarkus with GraalVM. This analysis examines how these fundamentally different runtime strategies impact behavior under various concurrency levels.
Test Methodology and Architecture
The test architecture simulated a realistic financial services workload with the following components:
- HTTP POST request processing: Each request published a message to Kafka, which was then consumed and persisted to PostgreSQL
- Hardware: Apple Mac Mini M4 (ARM64) with 24GB RAM running macOS
- Containerization: Docker with 1 CPU per service
- Memory constraints: Spring Boot allocated 256MB, Quarkus Native allocated 128MB
- Monitoring stack: Prometheus, Grafana, and Micrometer (for both frameworks)
The comparison intentionally avoids an apples-to-apples framework evaluation. Instead, it examines how runtime strategy—traditional JVM execution versus native compilation—affects performance characteristics.
Important Disclaimer
This is not a pure framework speed comparison but a comparison between two different runtime strategies:
- Spring Boot running on the JVM
- Quarkus compiled to a GraalVM native image
The purpose is not to declare a winner but to understand how runtime choices impact performance, resource usage, and scalability in real-world conditions.
Startup Time Comparison
Before stress testing, cold startup time was measured inside the container:
- Quarkus Native (3.31.1): 0.118 seconds
- Spring Boot 3.5.7 (JVM): Root WebApplicationContext initialized in 1492 ms (~1.49 seconds)
This represents roughly a 12× faster startup time for Quarkus Native. This difference has significant operational implications:
- Faster container scaling
- Better recovery from crashes
- Improved behavior in spot/preemptible environments
- Reduced cold start impact in serverless-style deployments
While startup time doesn't directly affect steady-state throughput, it dramatically impacts operational elasticity and cost optimization strategies.
Stress Test Results Analysis
The tests were conducted using wrk with different concurrency levels. Let's examine the results across each scenario.
Test 1: 10 Concurrent Connections
HTTP Throughput & Latency
Under 10 concurrent connections:
- Spring Boot processed ~3.4× more requests per second (3939.24 vs 1168.78)
- Spring achieved significantly higher HTTP throughput
- Spring saturated the CPU (100%), while Quarkus peaked at ~71%
However, Quarkus showed lower average latency per request (6.86ms vs 12.44ms) despite processing fewer requests per second. This suggests:
- Quarkus handles individual requests efficiently
- Spring increases throughput at the cost of higher average latency
- Spring likely increases internal concurrency to maximize CPU utilization
This reflects a classical trade-off: Spring → Higher throughput, Quarkus → Lower per-request latency
CPU Utilization
- Spring fully saturated the allocated CPU (100%)
- Quarkus peaked around ~70%
This means Spring extracted maximum processing capacity from the allocated core, while Quarkus left ~30% CPU headroom. If raw throughput is the goal, Spring utilized available compute resources more aggressively.
Memory Usage
The memory difference was significant:
- Quarkus Native: ~40 MB
- Spring Boot: ~256 MB
That's roughly 6× higher memory usage for Spring. From a container density perspective, Quarkus Native is substantially more memory-efficient—a difference particularly relevant in high-density container deployments or memory-constrained environments.
Kafka Consumption Behavior
In equal-volume scenarios:
- Both frameworks inserted the same total number of messages
- Quarkus drained the Kafka backlog ~45 seconds faster
Summary Under 10 Connections
| Metric | Spring Boot | Quarkus Native |
|---|---|---|
| HTTP Throughput | Higher (~3.4×) | Lower |
| CPU Utilization | 100% | ~70% |
| Memory Usage | Higher (256MB) | Much Lower (40MB) |
| Average Latency | Higher (12.44ms) | Lower (6.86ms) |
| Kafka Drain Time | Slower | Faster |
Test 2: 50 Concurrent Connections
HTTP Throughput & Latency
Under 50 concurrent connections:
- Spring Boot processed more requests per second (7123 vs 6038)
- The throughput gap narrowed compared to the 10-connection test
- Both frameworks scaled significantly relative to low concurrency
However, Quarkus maintained significantly lower latency (~8.31ms vs 17.13ms). This suggests:
- Quarkus continues to handle individual requests more efficiently
- Spring increases throughput by pushing concurrency harder
- Latency under load grows more aggressively in Spring
The same trade-off is reinforced: Spring → Higher throughput, Quarkus → Lower latency stability
CPU Utilization
- Spring saturated CPU at 100%
- Quarkus reached ~96.7%
At this concurrency level, both frameworks effectively utilized nearly the full CPU capacity. The previous CPU utilization gap largely disappeared, indicating that under higher concurrency, Quarkus is capable of fully utilizing allocated CPU resources.
The throughput difference here is no longer explained by unused CPU but more likely by:
- Internal threading model differences
- HTTP stack implementation
- Serialization overhead
- Framework-level concurrency strategies
Memory Usage
Memory efficiency remained highly differentiated:
- Quarkus Native: ~58.8 MB
- Spring Boot: ~256 MB
Spring maintained roughly 4–5× higher container memory usage. Heap usage showed Quarkus (native compatibility metrics) at ~38.4 MB versus Spring JVM heap at ~101 MB.
Kafka Consumption Behavior
At 50 connections:
- Both frameworks consumed Kafka messages at approximately the same time
- Total inserted messages were identical: 1,580,540 records each
- However, Spring produced more total messages during the test (~1,706,765 vs ~1,448,099)
Summary Under 50 Connections
| Metric | Spring Boot | Quarkus Native |
|---|---|---|
| HTTP Throughput | Higher | Slightly Lower |
| CPU Utilization | 100% | ~97% |
| Memory Usage | Higher (256MB) | Significantly Lower (59MB) |
| Average Latency | Higher (17.13ms) | Much Lower (8.31ms) |
| Kafka Consumption Time | Equivalent | Equivalent |
| Total Messages Produced | Higher | Lower |
Test 3: 200 Concurrent Connections
HTTP Throughput & Latency
Under 200 concurrent connections:
- Quarkus Native significantly outperformed Spring in throughput
- Quarkus: ~9,942 requests/sec
- Spring: ~7,340 requests/sec
- This represents a ~35% higher HTTP throughput for Quarkus
- Both frameworks reached full CPU saturation
At the same time, Quarkus maintained lower average latency (~24.47ms vs 33.18ms). This is an important shift from lower concurrency tests:
- At high concurrency, Quarkus not only preserves latency efficiency but also overtakes Spring in raw throughput
- Quarkus scales more efficiently under heavy parallel load
- Spring's throughput advantage at low concurrency does not persist at higher concurrency
- Native execution overhead remains stable even under CPU saturation
CPU Utilization
Both frameworks saturated CPU at 100%, with both limited by the same 1-core container constraint. This means throughput differences are not caused by CPU underutilization but by internal framework efficiency under load.
At this point, Quarkus converted CPU cycles into more HTTP throughput than Spring.
Memory Usage
Memory efficiency remained dramatically different:
- Quarkus Native: ~71.5 MB
- Spring Boot: ~256 MB
Even under heavy load, Quarkus consumed less than one-third of Spring's container memory. Spring memory remained flat at the container limit. Heap usage showed Quarkus compatibility metric at ~45.9 MB versus Spring JVM heap at ~101 MB.
Kafka Consumption Behavior
Under 200 connections:
- Both frameworks inserted the same total number of records: 2,075,484 messages each
- Quarkus produced significantly more total messages (~2,387,534 vs ~1,763,434)
- Despite producing fewer messages, Spring drained Kafka 90 seconds faster
Summary Under 200 Connections
| Metric | Spring Boot | Quarkus Native |
|---|---|---|
| HTTP Throughput | Lower | Highest (~35% more) |
| CPU Utilization | 100% | 100% |
| Memory Usage | Higher (256MB) | Significantly Lower (72MB) |
| Average Latency | Higher (33.18ms) | Lower (24.47ms) |
| Total Messages Produced | Lower | Higher |
| Kafka Drain Time | Faster | Slower |
Test 4: Extreme Concurrency and Memory Limits
The final test could not be completed as planned. During execution, the Spring Boot container was terminated due to an out-of-memory (OOM) condition. Inspection of the container state revealed:
- Exit code 137: Process was killed by the system (SIGKILL)
- OOMKilled=true: Container exceeded its memory limit
The Docker memory limit for Spring was set to 256MB, which was insufficient under this load. This highlights a critical aspect of the comparison: under extreme concurrency, Spring Boot (running on the JVM) required more memory than the configured container limit allowed, leading to forced termination.
Meanwhile, Quarkus Native continued operating within its allocated memory constraints, demonstrating superior resilience under memory pressure.
Operational Implications
The benchmark revealed several important operational considerations:
Container Density and Resource Efficiency
Quarkus Native consistently demonstrated significantly lower memory usage across all concurrency levels. This translates directly to:
- Higher container density per host
- Lower infrastructure costs
- Better utilization of memory-constrained environments
- Reduced need for memory overprovisioning
Scalability Patterns
The frameworks exhibited different scaling behaviors:
- Spring Boot: Excelled at low concurrency with aggressive CPU utilization but struggled at higher concurrency
- Quarkus Native: Demonstrated more consistent performance across concurrency levels, eventually outperforming Spring at high load
Resilience Under Pressure
The OOM condition in the final test demonstrates a critical operational difference:
- Spring Boot can hit hard memory limits under sustained load
- Quarkus Native maintains stable memory usage even under extreme conditions
This has significant implications for:
- Spot/preemptible instance usage
- Auto-scaling scenarios
- Environments with strict memory quotas
Startup Time and Elasticity
The 12× faster startup time of Quarkus Native enables:
- Faster container scaling events
- Quicker recovery from failures
- Better performance in serverless architectures
- Reduced cold start penalties
Framework vs Runtime Strategy
The most significant finding wasn't about which framework is faster, but how runtime strategy impacts behavior more than framework choice. The comparison between Spring Boot on JVM and Quarkus Native revealed fundamentally different performance characteristics:
- Memory efficiency: Native compilation consistently outperformed JVM execution
- Scaling behavior: Different concurrency levels favored different approaches
- Operational resilience: Native compilation demonstrated better memory stability
- Startup characteristics: Native compilation enabled dramatically faster initialization
This suggests that for applications with specific operational constraints, runtime strategy may be a more important consideration than framework selection.
Conclusion and Recommendations
The benchmark demonstrated that there's no universal winner between JVM and native execution approaches. The optimal choice depends on specific workload characteristics, infrastructure constraints, and operational objectives.
When to Consider Spring Boot on JVM
- Applications with abundant memory resources
- Scenarios where raw throughput at low concurrency is critical
- Environments with mature JVM tuning expertise
- Applications with complex reflection-heavy dependencies that don't GraalVM compatibility
When to Consider Quarkus Native
- Memory-constrained environments or high-density deployments
- Spot/preemptible instance usage where fast startup matters
- Serverless or container-based architectures with cold start penalties
- Scenarios requiring predictable memory usage under variable load
- Applications with compatible GraalVM native image requirements
The decision should be guided by workload profile, infrastructure constraints, and operational goals—not by hype. For financial systems with aggressive cost optimization strategies and deployment on spot instances, the native compilation approach offers compelling advantages in resource efficiency, startup time, and operational resilience.
Further Exploration
For organizations considering this transition, the following resources provide valuable information:
- Quarkus Documentation
- GraalVM Native Image Documentation
- Spring Boot Production Best Practices
- JVM vs Native Performance Analysis
Understanding these trade-offs enables more informed architectural decisions that align with specific operational requirements rather than following one-size-fits-all approaches.

Comments
Please log in or register to join the discussion