<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Bruno Minato</title>
    <description>The latest articles on Forem by Bruno Minato (@bruno_minato).</description>
    <link>https://forem.com/bruno_minato</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3780022%2F7b5a065f-b71b-4e80-8254-5dd49553c0c1.jpg</url>
      <title>Forem: Bruno Minato</title>
      <link>https://forem.com/bruno_minato</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bruno_minato"/>
    <language>en</language>
    <item>
      <title>From JVM to Native: My Performance Experiments with Spring Boot and Quarkus</title>
      <dc:creator>Bruno Minato</dc:creator>
      <pubDate>Wed, 18 Feb 2026 20:54:57 +0000</pubDate>
      <link>https://forem.com/bruno_minato/from-jvm-to-native-my-performance-experiments-with-spring-boot-and-quarkus-2je2</link>
      <guid>https://forem.com/bruno_minato/from-jvm-to-native-my-performance-experiments-with-spring-boot-and-quarkus-2je2</guid>
      <description>&lt;p&gt;Throughout my work on financial systems, most of the services I’ve built were based on Spring Boot. It’s the default choice in many enterprise environments, mature, well-documented, and widely adopted in production.&lt;br&gt;
However, many of these services run in environments with traffic spikes and aggressive cost optimization strategies. In some cases, we deployed on AWS Spot instances, which can be terminated at any time when the spot price exceeds the configured bid. &lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast startup matters&lt;/li&gt;
&lt;li&gt;Resource efficiency matters&lt;/li&gt;
&lt;li&gt;Predictable performance under constrained CPU and memory matters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To optimize costs while maintaining performance, I started exploring alternatives within the Java ecosystem. Although there are several high-performance backend languages such as Go, Rust, and Node.js, I wanted to stay within Java and evaluate another approach I had been reading about frequently:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quarkus compiled to a native image using GraalVM.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The goal was simple:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Can Quarkus Native be a viable alternative to Spring Boot in production scenarios where startup time, memory usage, and container efficiency are critical?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;⚠️ Important Disclaimer&lt;br&gt;
This is not a pure framework speed comparison.&lt;br&gt;
This article compares:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spring Boot running on the JVM&lt;/li&gt;
&lt;li&gt;Quarkus compiled to a GraalVM native image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is intentionally not apples-to-apples.&lt;br&gt;
It is a comparison between two different runtime strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A traditional JVM-based application&lt;/li&gt;
&lt;li&gt;A native-compiled Java application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The purpose is not to declare a winner, but to explore how runtime choices impact performance, resource usage, and scalability in real-world conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftoj5ik4e91wr0enpuqfh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftoj5ik4e91wr0enpuqfh.png" alt=" " width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workload&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;HTTP POST request&lt;/li&gt;
&lt;li&gt;Each request:&lt;/li&gt;
&lt;li&gt;Publishes message to Kafka&lt;/li&gt;
&lt;li&gt;Consumer reads message&lt;/li&gt;
&lt;li&gt;Persists to PostgreSQL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Host Machine&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apple Mac Mini M4 (ARM64)&lt;/li&gt;
&lt;li&gt;24 GB RAM&lt;/li&gt;
&lt;li&gt;macOS&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Container Limits&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 CPU per service&lt;/li&gt;
&lt;li&gt;Spring Boot: 256 MB (128 MB also ran initially, but under sustained load the container was eventually killed due to out-of-memory).&lt;/li&gt;
&lt;li&gt;Quarkus Native: 128 MB&lt;/li&gt;
&lt;li&gt;Kafka + PostgreSQL shared&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Metrics stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prometheus&lt;/li&gt;
&lt;li&gt;Grafana&lt;/li&gt;
&lt;li&gt;Micrometer (Spring + Quarkus)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Startup Time Comparison&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before running any stress tests, I measured cold startup time inside the container.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quarkus Native (3.31.1)&lt;/strong&gt; Started in &lt;strong&gt;0.118 seconds&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spring Boot 3.5.7 (JVM)&lt;/strong&gt; Root WebApplicationContext initialized in &lt;strong&gt;1492 ms (~1.49 seconds)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This represents roughly a &lt;strong&gt;12× faster startup time for Quarkus Native&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why this matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster container scaling&lt;/li&gt;
&lt;li&gt;Faster recovery from crashes&lt;/li&gt;
&lt;li&gt;Better behavior in Spot / preemptible environments&lt;/li&gt;
&lt;li&gt;Reduced cold start impact in serverless-style deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Startup time does not directly impact steady-state throughput, but it significantly impacts operational elasticity and cost optimization strategies.&lt;/p&gt;

&lt;h2&gt;
  
  
  First stress test
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;wrk -t4 -c10 -d120s -s post_c10.lua http://127.0.0.1:8082/event &amp;amp;&lt;br&gt;
wrk -t4 -c10 -d120s -s post_c10.lua http://127.0.0.1:8083/event &amp;amp;&lt;br&gt;
wait&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg9mphrn60c47oe7i8axz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg9mphrn60c47oe7i8axz.png" alt=" " width="725" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxet99tq1l51c7nmyn4s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxet99tq1l51c7nmyn4s.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F63176v0sy5awamsgxjis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F63176v0sy5awamsgxjis.png" alt=" " width="743" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;quarkus-native&lt;/th&gt;
&lt;th&gt;spring-app&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;host&lt;/td&gt;
&lt;td&gt;&lt;a href="http://127.0.0.1:8082" rel="noopener noreferrer"&gt;http://127.0.0.1:8082&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://127.0.0.1:8083" rel="noopener noreferrer"&gt;http://127.0.0.1:8083&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;connections&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;req/sec&lt;/td&gt;
&lt;td&gt;1168.78&lt;/td&gt;
&lt;td&gt;3939.24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;avg latency&lt;/td&gt;
&lt;td&gt;6.86ms&lt;/td&gt;
&lt;td&gt;12.44ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cpu max&lt;/td&gt;
&lt;td&gt;71.4%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cpu units max&lt;/td&gt;
&lt;td&gt;0.714&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory usage container max&lt;/td&gt;
&lt;td&gt;40mb&lt;/td&gt;
&lt;td&gt;256mb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap JVM memory used bytes max&lt;/td&gt;
&lt;td&gt;29mb&lt;/td&gt;
&lt;td&gt;102mb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kafka consumed&lt;/td&gt;
&lt;td&gt;45 seconds faster than spring&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;total messages inserted&lt;/td&gt;
&lt;td&gt;613195&lt;/td&gt;
&lt;td&gt;613195&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;total messages by framework created&lt;/td&gt;
&lt;td&gt;280682&lt;/td&gt;
&lt;td&gt;945708&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;For Quarkus Native, jvm&lt;/em&gt; metrics represent native process memory compatibility metrics rather than traditional JVM heap pools._&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results Under 10 Concurrent Connections&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;HTTP Throughput &amp;amp; Latency&lt;/p&gt;

&lt;p&gt;Under 10 concurrent connections:&lt;br&gt;
    • Spring Boot processed ~3.4× more requests per second.&lt;br&gt;
    • Spring achieved significantly higher HTTP throughput.&lt;br&gt;
    • Spring saturated the CPU (100%), while Quarkus did not.&lt;/p&gt;

&lt;p&gt;This indicates that at low concurrency:&lt;/p&gt;

&lt;p&gt;Spring utilized the CPU more aggressively and converted it into higher HTTP throughput.&lt;/p&gt;

&lt;p&gt;At the same time:&lt;br&gt;
    • Quarkus showed lower average latency per request.&lt;br&gt;
    • Even while processing fewer requests per second.&lt;/p&gt;

&lt;p&gt;This suggests:&lt;br&gt;
    • Quarkus handles individual requests efficiently.&lt;br&gt;
    • Spring increases throughput at the cost of higher average latency.&lt;br&gt;
    • Spring likely increases internal concurrency to maximize CPU utilization.&lt;/p&gt;

&lt;p&gt;This reflects a classical trade-off:&lt;br&gt;
    • Spring → Higher throughput&lt;br&gt;
    • Quarkus → Lower per-request latency&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CPU Utilization&lt;/strong&gt;&lt;br&gt;
    • Spring fully saturated the allocated CPU (100%).&lt;br&gt;
    • Quarkus peaked around ~70%.&lt;/p&gt;

&lt;p&gt;This means:&lt;br&gt;
    • Spring extracted maximum processing capacity from the allocated core.&lt;br&gt;
    • Quarkus left ~30% CPU headroom in this scenario.&lt;/p&gt;

&lt;p&gt;If raw throughput is the goal:&lt;/p&gt;

&lt;p&gt;Spring utilized the available compute resources more aggressively.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The memory difference was significant:&lt;br&gt;
    • Quarkus Native: ~40 MB&lt;br&gt;
    • Spring Boot: ~256 MB&lt;/p&gt;

&lt;p&gt;That is roughly 6× higher memory usage for Spring.&lt;/p&gt;

&lt;p&gt;From a container density perspective:&lt;/p&gt;

&lt;p&gt;Quarkus Native is substantially more memory-efficient.&lt;/p&gt;

&lt;p&gt;This difference becomes particularly relevant in high-density container deployments or memory-constrained environments.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kafka Consumption Behavior&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In equal-volume scenarios:&lt;br&gt;
    • Both frameworks inserted the same total number of messages.&lt;br&gt;
    • Quarkus drained the Kafka backlog ~45 seconds faster.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary Under 10 Connections&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spring Boot&lt;br&gt;
    • Higher HTTP throughput&lt;br&gt;
    • Fully utilized CPU&lt;br&gt;
    • Higher memory usage&lt;br&gt;
    • Higher average latency&lt;/p&gt;

&lt;p&gt;Quarkus Native&lt;br&gt;
    • Lower HTTP throughput&lt;br&gt;
    • Lower CPU usage&lt;br&gt;
    • Much smaller memory footprint&lt;br&gt;
    • Lower latency&lt;br&gt;
    • Faster Kafka drain&lt;/p&gt;

&lt;h2&gt;
  
  
  Second stress test
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;wrk -t4 -c50  -d120s -s post_c50.lua http://127.0.0.1:8082/event &amp;amp;&lt;br&gt;
wrk -t4 -c50  -d120s -s post_c50.lua http://127.0.0.1:8083/event &amp;amp;&lt;br&gt;
wait&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkevnnbs44fi5ufk8bnwj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkevnnbs44fi5ufk8bnwj.png" alt=" " width="735" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0icf3we76w4axw7fwn1x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0icf3we76w4axw7fwn1x.png" alt=" " width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zbagrxcrs8rkfn8qwi2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6zbagrxcrs8rkfn8qwi2.png" alt=" " width="748" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;quarkus-native&lt;/th&gt;
&lt;th&gt;spring-app&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;host&lt;/td&gt;
&lt;td&gt;&lt;a href="http://127.0.0.1:8082" rel="noopener noreferrer"&gt;http://127.0.0.1:8082&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://127.0.0.1:8083" rel="noopener noreferrer"&gt;http://127.0.0.1:8083&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;connections&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;req/sec&lt;/td&gt;
&lt;td&gt;6038.77&lt;/td&gt;
&lt;td&gt;7123.39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;avg latency&lt;/td&gt;
&lt;td&gt;8.31ms&lt;/td&gt;
&lt;td&gt;17.13ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cpu max&lt;/td&gt;
&lt;td&gt;96.7%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cpu units max&lt;/td&gt;
&lt;td&gt;0.967&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory usage container max&lt;/td&gt;
&lt;td&gt;58.8mb&lt;/td&gt;
&lt;td&gt;256mb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap JVM memory used bytes max&lt;/td&gt;
&lt;td&gt;38.4mb&lt;/td&gt;
&lt;td&gt;101mb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kafka consumed&lt;/td&gt;
&lt;td&gt;consumed same time&lt;/td&gt;
&lt;td&gt;consumed same time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;total messages inserted&lt;/td&gt;
&lt;td&gt;1580540&lt;/td&gt;
&lt;td&gt;1580540&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;total messages by framework created&lt;/td&gt;
&lt;td&gt;1448099&lt;/td&gt;
&lt;td&gt;1706765&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;For Quarkus Native, jvm&lt;/em&gt; metrics represent native process memory compatibility metrics rather than traditional JVM heap pools._&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results Under 50 Concurrent Connections&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;HTTP Throughput &amp;amp; Latency&lt;/p&gt;

&lt;p&gt;Under 50 concurrent connections:&lt;br&gt;
    • Spring Boot processed more requests per second (7123 vs 6038).&lt;br&gt;
    • The throughput gap narrowed compared to the 10-connection test.&lt;br&gt;
    • Both frameworks scaled significantly relative to low concurrency.&lt;/p&gt;

&lt;p&gt;However:&lt;br&gt;
    • Quarkus maintained significantly lower latency.&lt;br&gt;
    • Quarkus: ~8.31 ms&lt;br&gt;
    • Spring: ~17.13 ms&lt;/p&gt;

&lt;p&gt;This suggests:&lt;br&gt;
    • Quarkus continues to handle individual requests more efficiently.&lt;br&gt;
    • Spring increases throughput by pushing concurrency harder.&lt;br&gt;
    • Latency under load grows more aggressively in Spring.&lt;/p&gt;

&lt;p&gt;This reinforces the same trade-off observed earlier:&lt;br&gt;
    • Spring → Higher throughput&lt;br&gt;
    • Quarkus → Lower latency stability&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CPU Utilization&lt;/strong&gt;&lt;br&gt;
    • Spring saturated CPU at 100%.&lt;br&gt;
    • Quarkus reached ~96.7%.&lt;/p&gt;

&lt;p&gt;At this concurrency level:&lt;br&gt;
    • Both frameworks effectively utilized nearly the full CPU capacity.&lt;br&gt;
    • The previous CPU utilization gap (seen at 10 connections) largely disappeared.&lt;/p&gt;

&lt;p&gt;This indicates:&lt;/p&gt;

&lt;p&gt;Under higher concurrency, Quarkus is capable of fully utilizing allocated CPU resources.&lt;/p&gt;

&lt;p&gt;The throughput difference here is no longer explained by unused CPU, but more likely by:&lt;br&gt;
    • Internal threading model differences&lt;br&gt;
    • HTTP stack implementation&lt;br&gt;
    • Serialization overhead&lt;br&gt;
    • Framework-level concurrency strategies&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Memory efficiency remained highly differentiated:&lt;br&gt;
    • Quarkus Native: ~58.8 MB&lt;br&gt;
    • Spring Boot: ~256 MB&lt;/p&gt;

&lt;p&gt;Spring maintained roughly 4–5× higher container memory usage.&lt;/p&gt;

&lt;p&gt;Heap usage:&lt;br&gt;
    • Quarkus (native compatibility metrics): ~38.4 MB&lt;br&gt;
    • Spring JVM heap: ~101 MB&lt;/p&gt;

&lt;p&gt;From a container density perspective:&lt;/p&gt;

&lt;p&gt;Quarkus Native still provides a substantial memory advantage under moderate concurrency.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kafka Consumption Behavior&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At 50 connections:&lt;br&gt;
    • Both frameworks consumed Kafka messages at approximately the same time.&lt;br&gt;
    • Total inserted messages were identical:&lt;br&gt;
    • 1,580,540 records each&lt;/p&gt;

&lt;p&gt;However:&lt;br&gt;
    • Spring produced more total messages during the test:&lt;br&gt;
    • Spring: ~1,706,765&lt;br&gt;
    • Quarkus: ~1,448,099&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary Under 50 Connections&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spring Boot&lt;br&gt;
    • Higher HTTP throughput&lt;br&gt;
    • Fully saturated CPU&lt;br&gt;
    • Higher memory footprint&lt;br&gt;
    • Higher average latency&lt;/p&gt;

&lt;p&gt;Quarkus Native&lt;br&gt;
    • Slightly lower HTTP throughput&lt;br&gt;
    • Nearly full CPU utilization&lt;br&gt;
    • Significantly lower memory usage&lt;br&gt;
    • Much lower average latency&lt;br&gt;
    • Equivalent Kafka consumption time&lt;/p&gt;

&lt;h2&gt;
  
  
  Third stress test
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;wrk -t4 -c200  -d120s -s post_c50.lua http://127.0.0.1:8082/event &amp;amp;&lt;br&gt;
wrk -t4 -c200  -d120s -s post_c50.lua http://127.0.0.1:8083/event &amp;amp;&lt;br&gt;
wait&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foymyd6kwtwtk78c5jq69.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foymyd6kwtwtk78c5jq69.png" alt=" " width="743" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxv3q5yzndu2khfkz20y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxv3q5yzndu2khfkz20y.png" alt=" " width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgvm2yfm1b62wa2e0o47p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgvm2yfm1b62wa2e0o47p.png" alt=" " width="755" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;quarkus-native&lt;/th&gt;
&lt;th&gt;spring-app&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;host&lt;/td&gt;
&lt;td&gt;&lt;a href="http://127.0.0.1:8082" rel="noopener noreferrer"&gt;http://127.0.0.1:8082&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="http://127.0.0.1:8083" rel="noopener noreferrer"&gt;http://127.0.0.1:8083&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;connections&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;req/sec&lt;/td&gt;
&lt;td&gt;9942.45&lt;/td&gt;
&lt;td&gt;7340.39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;avg latency&lt;/td&gt;
&lt;td&gt;24.47ms&lt;/td&gt;
&lt;td&gt;33.18ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cpu max&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cpu units max&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory usage container max&lt;/td&gt;
&lt;td&gt;71.5mb&lt;/td&gt;
&lt;td&gt;256mb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap JVM memory used bytes max&lt;/td&gt;
&lt;td&gt;45.9mb&lt;/td&gt;
&lt;td&gt;101mb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kafka consumed&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;90 seconds faster than quarkus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;total messages inserted&lt;/td&gt;
&lt;td&gt;2075484&lt;/td&gt;
&lt;td&gt;2075484&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;total messages by framework created&lt;/td&gt;
&lt;td&gt;2387534&lt;/td&gt;
&lt;td&gt;1763434&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;For Quarkus Native, jvm&lt;/em&gt; metrics represent native process memory compatibility metrics rather than traditional JVM heap pools._&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results Under 200 Concurrent Connections&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;HTTP Throughput &amp;amp; Latency&lt;/p&gt;

&lt;p&gt;Under 200 concurrent connections:&lt;br&gt;
    • Quarkus Native significantly outperformed Spring in throughput&lt;br&gt;
    • Quarkus: ~9,942 requests/sec&lt;br&gt;
    • Spring: ~7,340 requests/sec&lt;br&gt;
    • This represents a ~35% higher HTTP throughput for Quarkus.&lt;br&gt;
    • Both frameworks reached full CPU saturation.&lt;/p&gt;

&lt;p&gt;At the same time:&lt;br&gt;
    • Quarkus maintained lower average latency:&lt;br&gt;
    • Quarkus: ~24.47 ms&lt;br&gt;
    • Spring: ~33.18 ms&lt;/p&gt;

&lt;p&gt;This is an important shift from lower concurrency tests:&lt;/p&gt;

&lt;p&gt;At high concurrency, Quarkus not only preserves latency efficiency, it also overtakes Spring in raw throughput.&lt;/p&gt;

&lt;p&gt;This suggests:&lt;br&gt;
    • Quarkus scales more efficiently under heavy parallel load.&lt;br&gt;
    • Spring’s throughput advantage at low concurrency does not persist at higher concurrency.&lt;br&gt;
    • Native execution overhead remains stable even under CPU saturation.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CPU Utilization&lt;/strong&gt;&lt;br&gt;
    • Both frameworks saturated CPU at 100%.&lt;br&gt;
    • Both were limited by the same 1-core container constraint.&lt;/p&gt;

&lt;p&gt;This means:&lt;br&gt;
    • Throughput differences are not caused by CPU underutilization.&lt;br&gt;
    • The bottleneck shifted to internal framework efficiency under load.&lt;/p&gt;

&lt;p&gt;At this point:&lt;/p&gt;

&lt;p&gt;Quarkus converted CPU cycles into more HTTP throughput than Spring.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Memory efficiency remained dramatically different:&lt;br&gt;
    • Quarkus Native: ~71.5 MB&lt;br&gt;
    • Spring Boot: ~256 MB&lt;/p&gt;

&lt;p&gt;Even under heavy load:&lt;br&gt;
    • Quarkus consumed less than one-third of Spring’s container memory.&lt;br&gt;
    • Spring memory remained flat at the container limit.&lt;/p&gt;

&lt;p&gt;Heap usage:&lt;br&gt;
    • Quarkus compatibility metric: ~45.9 MB&lt;br&gt;
    • Spring JVM heap: ~101 MB&lt;/p&gt;

&lt;p&gt;From a container density perspective:&lt;/p&gt;

&lt;p&gt;Quarkus Native remains substantially more memory-efficient even at maximum concurrency.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kafka Consumption Behavior&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Under 200 connections:&lt;br&gt;
    • Both frameworks inserted the same total number of records:&lt;br&gt;
    • 2,075,484 messages each&lt;/p&gt;

&lt;p&gt;However:&lt;br&gt;
    • Quarkus produced significantly more total messages:&lt;br&gt;
    • Quarkus: ~2,387,534&lt;br&gt;
    • Spring: ~1,763,434&lt;/p&gt;

&lt;p&gt;Despite producing fewer messages:&lt;br&gt;
    • Spring drained Kafka 90 seconds faster.&lt;/p&gt;

&lt;p&gt;⸻&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary Under 200 Connections&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Quarkus Native&lt;br&gt;
    • Highest HTTP throughput&lt;br&gt;
    • Lower average latency&lt;br&gt;
    • Full CPU utilization&lt;br&gt;
    • Significantly lower memory footprint&lt;br&gt;
    • Produced more total messages&lt;br&gt;
    • Slower Kafka drain in this specific workload&lt;/p&gt;

&lt;p&gt;Spring Boot&lt;br&gt;
    • Lower HTTP throughput at high concurrency&lt;br&gt;
    • Higher latency&lt;br&gt;
    • Full CPU utilization&lt;br&gt;
    • Much higher memory usage&lt;br&gt;
    • Faster Kafka drain&lt;/p&gt;

&lt;h2&gt;
  
  
  Fourth and Last Stress Test
&lt;/h2&gt;

&lt;p&gt;Unfortunately, this test could not be completed.&lt;/p&gt;

&lt;p&gt;During execution, the &lt;code&gt;spring-app&lt;/code&gt; container was terminated due to an out-of-memory (OOM) condition.&lt;/p&gt;

&lt;p&gt;To confirm the cause, I inspected the container state:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrysnmdp70o8qd72br7z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrysnmdp70o8qd72br7z.png" alt=" " width="800" height="59"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this means&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exit code 137 indicates the process was killed by the system (SIGKILL).&lt;/li&gt;
&lt;li&gt;OOMKilled=true confirms the container exceeded its memory limit.&lt;/li&gt;
&lt;li&gt;The Docker memory limit for Spring was set to 256 MB, which was insufficient under this load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This highlights an important aspect of the comparison:&lt;/p&gt;

&lt;p&gt;Under extreme concurrency, Spring Boot (running on the JVM) required more memory than the configured container limit allowed, leading to forced termination.&lt;/p&gt;

&lt;p&gt;Meanwhile, Quarkus Native continued operating within its allocated memory constraints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts and Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This benchmark was not about declaring a universal winner.&lt;/p&gt;

&lt;p&gt;It was about understanding how runtime strategy impacts real-world behavior under constrained CPU and memory.&lt;/p&gt;

&lt;p&gt;The most interesting finding wasn’t “which is faster,” but how differently they scale under pressure.&lt;/p&gt;

&lt;p&gt;Choosing between JVM and native execution can impact performance characteristics more than choosing between frameworks.&lt;/p&gt;

&lt;p&gt;And that decision should be guided by workload profile, infrastructure constraints, and operational goals, not by hype.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>java</category>
      <category>performance</category>
      <category>springboot</category>
    </item>
  </channel>
</rss>
