Quarkus Native vs JIT: The Banking Reality Check

Quarkus native cuts startup to 17ms and RSS to 37MB - but Quarkus's own 2026 lab says going native roughly halves throughput. A banking decision guide.

TL;DR

  • Quarkus native vs JIT in one line: native buys startup and density, JIT buys throughput and diagnostics. Only the price of that trade changed in 2026.
  • The Quarkus team’s own reproducible lab says going native cuts throughput roughly in half on a CPU-bound workload. Independent I/O-bound tests show a narrower gap. Plan against −8% to −50%.
  • In a bank the deciding factor is usually not performance. It is the library stack: FIPS crypto, JAXP/XML, Oracle JDBC, Liquibase.
  • The answer is a hybrid estate: JIT on the money path, native on the elastic edge.

GraalVM native-image gives you a Quarkus service that starts in 17 milliseconds and holds 37 MB of RSS. Then, on 2 March 2026, Quarkus said the quiet part out loud in its own benchmark write-up. In a bank that redraws the decision map: the workloads you most want to shrink cannot afford to lose half their throughput.

Quarkus native vs JIT: two runtimes, one programming model

On the JVM: build-time processing — CDI wiring, config, annotation scanning — then plain bytecode. At runtime, C2 or the Graal JIT, your pick of G1, ZGC or Shenandoah, plus reflection, agents, heap dumps and JFR behaving as your Java teams expect. Warmup is real, paid once per pod.

Native is a different contract. native-image runs static reachability analysis under a closed-world assumption, then AOT-compiles what it sees into a standalone SubstrateVM binary. No JIT: class loading, linking and much initialisation happen at build time — hence millisecond startup. Reflection, dynamic proxies, resources and serialization all need reachability metadata, and missing metadata is an outage, not a log line.

Mandrel 25 is the LTS native builder; Quarkus 3.36 raised the minimum to GraalVM/Mandrel 25.0. Latest Quarkus is 3.39.1 (August 2026); LTS streams are 3.33.x and 3.27.x. Both shipped emergency releases on 5 August 2026 fixing a CVE and a native-image regression — native carries its own defect surface.

The Quarkus native throughput penalty, measured by Quarkus

From Holly Cummins on the Quarkus blog: “if you compare Quarkus on JVM against Quarkus native, the trade-off is back… Going native cuts throughput in half. (For Spring Boot, the native penalty is similar.) For most applications, this trade-off isn’t worth it.” (quarkus.io/blog/new-benchmarks/)

It comes from the project that benefits from saying the opposite, the methodology is defensible (qDup plus Hyperfoil, so no coordinated omission, with cgroup and cpuset isolation), and it ships as a reproducible repository.

Two independent tests point the other way. A single-author I/O-bound benchmark of MCP servers (February 2026) measured Quarkus JVM at 4,739 RPS against native at 3,449 RPS — about −27% — RSS 194 MB versus 36 MB, P95 8.13 ms versus 15.92 ms. By its own configuration table, it built the Quarkus native image on GraalVM 23, below the supported minimum, while the Spring native variants ran on GraalVM 25. Directional only. A single developer’s public GitHub lab reported REST at 24,198 req/s JVM versus 22,295 native — roughly −8% — startup 436.4 ms versus 9.2 ms, post-load RSS 193.8 MiB versus 35.3 MiB.

The honest 2026 range is −8% to −50%, with the vendor’s CPU-bound lab at the bad end. Native looks best exactly where CPU is not the bottleneck. Note the P95: no warmup cliff on a cold pod, worse steady-state tail once warm.

What Quarkus native buys — and what Leyden buys instead

The footprint case is not in dispute. Quarkus’s own Leyden write-up measured a small REST app at 370 ms / 122 MB RSS / 456 MB image on fast-jar against 17 ms / 37 MB / 155 MB native; a large CRUD app went from 3,189 ms / 580 MB to 242 ms / 210 MB. The authors are explicit that these came off laptops, not an isolated lab, and that they collected no throughput numbers.

Leyden AOT caching (Quarkus 3.32+, JDK 25) is narrower than the hype: 80 ms startup on the small app, but 103 MB RSS and a bigger image at 495 MB. It trades disk for startup — the “keep JIT, start faster” lever, not a native substitute.

Dimension JVM fast-jar JVM + Leyden Native (Mandrel)
Startup (small REST app) 370 ms 80 ms 17 ms
RSS (small REST app) 122 MB 103 MB 37 MB
Container image 456 MB 495 MB 155 MB
Throughput Baseline Not published Roughly −8% to −50%
Diagnostics Full JFR, heap dumps, agents Full JFR, heap dumps, agents Build-time opt-in; gdb for debug

⚠️ Warning: CRaC is not a supportable third runtime on Quarkus today — narrowed to opt-in in January 2026, no guide on quarkus.io, and an open issue on database connections failing across restore. The only documented snapshot path is AWS Lambda SnapStart, AWS-managed and not an on-prem or OpenShift option.

Native or JIT? A banking workload decision table

These are practitioner judgment calls for banking-shaped workloads, not benchmark results.

Workload archetype Prefer Why
Always-on core ledger / account service JIT Already warm; you want ZGC/G1 pause control and a heap dump at 2 a.m.
Channel API / mobile BFF Native RSS and image size dominate node packing on bursty retail traffic.
Scale-to-zero / Knative APIs Native Time-to-first-request is the product metric.
ISO 20022 initiation & card auth Native if XML, crypto and JDBC are extension-clean; else JIT The payload stack decides this, not the runtime.
EOD batch / interest accrual / clearing files JIT Throughput-bound with large heaps; Serial GC is the wrong collector.
Kafka consumers: long-lived vs short-lived JIT / Native Warmup is already paid on a long-lived consumer; burst workers scale from zero.
Vendor JAR with heavy reflection or custom JDBC JIT Non-extension drivers are “unlikely to function” natively.

Read the pattern, not the rows: a hybrid estate — JIT on the money path, native on the elastic edge. Pod lifetime and library risk draw the line, almost never the benchmark. If density is the CFO conversation on a long-lived service, shrink the JVM with Leyden and a tight heap first — the discipline behind any microservices sizing exercise.

Where Quarkus native breaks in a bank’s library stack

This argument is stronger than throughput. In the Quarkus tracker’s open area/native-image issues, failures cluster in the libraries a regulated stack cannot avoid.

  • FIPS crypto. BouncyCastle’s BC-FIPS provider fails the native build — #37500, open since 2024. If FIPS-validated crypto is a mandate, that one issue ends the conversation.
  • XML. Mandrel 25 broke the JAXP native integration test (#54910) — directly on the ISO 20022 path.
  • Database migrations. GraalVM 25.2 breaks the Liquibase native tests (#55113).
  • Oracle JDBC. Native library support needs extra configuration (#54479), and per the datasource guide, non-extension drivers “are unlikely to function when compiling your application into a native executable.”
  • Supply chain. The native SBOM does not record the GraalVM/Mandrel version (#53977). Expect that to fail an audit.

Diagnostics have improved. Mandrel 25 added JCMD, Native Memory Tracking and jdk.ObjectAllocationSample, so you can pull a thread dump or start a recording on a running binary. The catch: monitoring must be selected at build time via quarkus.native.monitoring, and events implemented by bytecode instrumentation on HotSpot — file I/O and exceptions among them — are still absent. Debug is gdb. Better than 2024; not parity.

quarkus.native.monitoring=jfr,heapdump,jvmstat,nmt,jcmd
quarkus.native.container-build=true
quarkus.native.builder-image=quay.io/quarkus/ubi9-quarkus-mandrel-builder-image:jdk-25
quarkus.native.native-image-xmx=5g


💡 Pro Tip: pin the builder image tag and build in a container, so the artefact never depends on whichever GraalVM a developer has installed. Budget the RAM — the native reference guide documents 6–8 GB RSS for the Hibernate ORM quickstart build, on a floor of 4 CPUs and 4 GB.

The India angle: what RBI and NPCI actually say

The RBI Master Direction on Outsourcing of Information Technology Services (RBI/2023-24/102) governs the provider relationship — board-approved policy, due diligence, audit and supervisory access, data location, and a documented, tested exit strategy. No RBI rule addresses JVMs, native compilation or container density.

The pressure it creates is portability: a bespoke, Oracle-GraalVM-licensed, PGO-tuned pipeline a successor provider cannot reproduce is harder to hand over than an OSS Mandrel/OpenJDK toolchain. That is reasoning, not a compliance requirement — but PGO and native G1 are Oracle GraalVM only. PGO became a Quarkus feature in 3.35 and roughly doubles build time. Mandrel gives you single-threaded Serial GC, plus a footgun: absent an explicit max heap, a native image defaults to 80% of physical memory, which interacts badly with a cgroup limit.

On UPI: NPCI’s circular of 26 April 2025, effective 16 June 2025, cut API response times to 15 seconds for request/response pay and 10 seconds for status check, reversal and address validation. Those are maximum permitted response windows — ceilings, not p99 SLAs, and not a statement about typical transaction latency. A 10-second ceiling is enormous next to any JVM warmup or GC pause, so NPCI’s timings do not justify going native. What bites in UPI is availability and technical declines, which native compilation does nothing for.

⚖️ Regulatory nuance. This article is not legal advice. RBI’s outsourcing directions govern the provider relationship, not your runtime; regulated workloads require demonstrable controls and a tested exit strategy. The recommendation here is architectural.

No Indian bank, NBFC or PSP has published a Quarkus or GraalVM-native case study. The only published bank story is Banco do Brasil’s Open Banking extractor — architecture only, no native-versus-JIT numbers, and no claim it runs native. If someone shows you an India reference, ask for the URL.

The cost math nobody publishes

Kubernetes schedules on requests, not usage, so RSS savings become node savings only if you lower the memory request. On the verified figures — 37 versus 122 MB, 35.3 versus 193.8 MiB post-load — that is roughly 3× to 5.5× more replicas per node. An upper bound, not a measurement. The real trade: native buys memory with CPU. Memory-constrained fleet, you win. CPU-constrained fleet, you can lose money: halved throughput means more replicas for the same TPS.

Key Takeaways

  • Native buys startup and density; JIT buys throughput and diagnostics. Only the price changed in 2026.
  • Quarkus’s own lab: roughly half the throughput on CPU-bound work; I/O-bound tests, a narrower gap. Plan against −8% to −50%.
  • The deciding factor in a bank is rarely performance. It is FIPS crypto, JAXP/XML, Oracle JDBC and Liquibase.
  • Build the hybrid estate: JIT on the money path, native on the elastic edge.
  • Develop on the JVM, test native with @QuarkusIntegrationTest, and compare warm-vs-warm and cold-vs-cold — never warm JIT against cold native.
  • Rule of thumb: if the pod lives long enough for C2 to finish, and a GC pause or a missing diagnostic can page the incident commander, stay on the JVM. If the pod may not exist until the request arrives, go native — after native integration tests pass on your real ISO, JDBC and Kafka stack.

Quarkus native vs JIT: FAQ

How much throughput does Quarkus native lose versus the JVM? Plan against −8% to −50%. Quarkus’s own CPU-bound lab reports roughly half; independent I/O-bound tests measured about −27% and about −8%.

Is Quarkus native faster than the JVM? On startup and memory, yes: 17 ms and 37 MB RSS against 370 ms and 122 MB on fast-jar for the same small REST app. On steady-state throughput and tail latency once warm, no.

Can I run FIPS crypto or Oracle JDBC in a Quarkus native image? Not reliably today. BC-FIPS fails the native build (#37500), Oracle JDBC needs extra configuration (#54479), and the datasource guide says non-extension drivers are “unlikely to function” natively.

Does Project Leyden replace GraalVM native image? No. Leyden AOT caching (Quarkus 3.32+, JDK 25) gives 80 ms startup but 103 MB RSS and a bigger 495 MB image — the “keep JIT, start faster” lever.

Which side of that line does your heaviest Quarkus service sit on? If you have run the comparison properly, I want the number: what throughput delta did you measure going native? Drop it in the comments — warm-vs-warm, with the workload shape.

Subscribe
Notify of

1 Comment
Oldest
Newest Most Voted

[…] in place, so steady-state performance is unchanged. If you are weighing that against native, our Quarkus Native vs JIT: the banking reality check walks through the same trade-off with production […]