Spring Boot on Fargate vs.
Quarkus native on Lambda
The same application, deployed two ways on AWS, load-tested identically. Run 8 to 9 September 2026 in us-east-1.
Every number below comes from a raw k6 summary or CloudWatch pull kept from that run. Where a figure is modeled rather than measured, it says so.
Summary
| Spring Boot on Fargate | Quarkus native on Lambda | Change | |
|---|---|---|---|
| Cold start | 54.50 s | 684 ms | 80x faster |
| Median latency | 92.85 ms | 26.55 ms | 3.5x faster |
| p99 latency | 218.25 ms | 50.16 ms | 4.4x faster |
| Worst case | 1,895.89 ms | 235.08 ms | 8.1x tighter |
| Throughput | 33.24 req/s | 37.79 req/s | +13.7% |
| Memory in use | ~340 MB | 166 MB peak | ~2x lower |
| Server-side errors | 0 | 0 | even |
The serverless rebuild was faster at every percentile, not only in the tail, and it did that while carrying TLS overhead the Fargate deployment was not paying.
Method
The application. Spring PetClinic REST. The Fargate deployment runs it as a single Spring Boot service. The Lambda deployment is the same application split into seven Quarkus modules, each compiled to a GraalVM native image and deployed as its own function behind its own API Gateway HTTP API.
The infrastructure. Both stacks ran in the same AWS account and region against the same PostgreSQL database. Fargate: 2 tasks at 0.5 vCPU and 1 GB each, behind an Application Load Balancer, connecting to RDS directly. Lambda: 1024 MB per function on arm64, connecting through RDS Proxy, which is the AWS-recommended pattern because bursty Lambda concurrency can otherwise exhaust Postgres connections.
The load test. k6, 20 virtual users, 5 minutes, constant VUs. Both scripts are identical in structure: the same weighted endpoint mix (owners 40%, pets 25%, vets 15%, visits 10%, pettypes 5%, specialties 5%) and the same 0.5 s sleep between iterations. The only difference is that the Lambda script targets seven API Gateway URLs where the Fargate script targets one base URL, which is inherent to the architectures rather than a change in test design.
One asymmetry, disclosed because it matters
The Fargate run shows TLS handshake time at zero throughout, meaning it served plain HTTP through the ALB. The Lambda run went over HTTPS through API Gateway, paying up to 76.9 ms of handshake. Lambda is carrying encryption overhead that Fargate is not, and still wins every latency measure below. Correcting this asymmetry would widen the gap, not narrow it.
Cold start
The largest single difference, and the one that decides whether serverless is viable for a given workload at all.
Two independent tasks at 54.806 s and 54.192 s, read from the application's own startup log line via CloudWatch Logs Insights. Reproducible across both tasks, not a one-off.
Averaged across 23 recorded cold starts spanning deployment and both load tests, taken from the init duration on CloudWatch report lines.
| Function | Cold starts | Avg init | Min | Max |
|---|---|---|---|---|
| visits | 3 | 600.48 ms | 483.05 ms | 664.33 ms |
| pettypes | 3 | 649.82 ms | 492.44 ms | 751.91 ms |
| vets | 4 | 664.80 ms | 573.88 ms | 719.32 ms |
| owners | 6 | 690.64 ms | 662.62 ms | 726.09 ms |
| pets | 5 | 703.38 ms | 681.02 ms | 768.30 ms |
| specialties | 2 | 826.03 ms | 707.76 ms | 944.30 ms |
The honest caveat
54.5 s is specific to a cost-optimized 0.5 vCPU task. JVM startup is CPU-bound, and these logs show it: parsing logback.xml alone took 1,090 ms, which is evidence of real CPU throttling at this task size. A larger Fargate task would start meaningfully faster. The 80x figure is accurate for this configuration and should not be read as the number for every Spring Boot deployment.
Latency under load
Two Quarkus runs were retained and both are shown. Earlier runs from the same session were not kept, so only these two are cited.
| Percentile | Fargate | Lambda run 4 | Lambda run 5 |
|---|---|---|---|
| min | 79.43 ms | 14.89 ms | 14.69 ms |
| median | 92.85 ms | 26.55 ms | 26.63 ms |
| average | 99.71 ms | 27.63 ms | 29.42 ms |
| p90 | 103.09 ms | 33.98 ms | 35.20 ms |
| p95 | 120.77 ms | 36.92 ms | 38.87 ms |
| p99 | 218.25 ms | 50.16 ms | 55.04 ms |
| max | 1,895.89 ms | 235.08 ms | 1,646.67 ms |
| requests | 9,989 | 11,358 | 11,316 |
| throughput | 33.24/s | 37.79/s | 37.66/s |
Run 5's outlier is real and is not hidden. One request took 1,646.67 ms, almost certainly a cold start caught mid-test. Run 4's worst case was 235 ms. The two runs agree closely everywhere else, including the median to within 0.08 ms, so the maximum is the only materially unstable figure between them. Any serverless deployment shows this shape: an excellent warm distribution with occasional cold-start spikes in the tail.
Why Lambda wins the median too. The intuition that an extra API Gateway hop must cost latency is correct in isolation, but it is outweighed here. Measured at the function, Lambda's own execution averaged 12.84 ms. The rest of the 26.55 ms the client observed is API Gateway, TLS and network. The Fargate side is spending its 92.85 ms inside a thread-pooled JVM on a throttled half-vCPU, and that dominates.
Reliability
Fargate: 9,989 requests, zero failures. Lambda: 11,358 requests with 4 client-side failures in run 4 and 11,316 with 30 in run 5. Those were not Lambda errors. CloudWatch reports zero errors for all six functions across 8,466 recorded invocations. The failures k6 saw were client-side, the expected consequence of the Lambda client opening connections to six separate TLS endpoints where the Fargate client used one load balancer. That is a property of the test topology, not of the platform.
| Function | Invocations | Avg duration | Max duration | Errors | Peak concurrency |
|---|---|---|---|---|---|
| owners | 3,477 | 11.31 ms | 845.81 ms | 0 | 7 |
| pets | 2,047 | 18.95 ms | 789.28 ms | 0 | 5 |
| vets | 1,278 | 11.41 ms | 790.36 ms | 0 | 4 |
| visits | 815 | 11.49 ms | 724.16 ms | 0 | 3 |
| pettypes | 442 | 7.58 ms | 773.34 ms | 0 | 2 |
| specialties | 407 | 8.16 ms | 769.14 ms | 0 | 2 |
| Total | 8,466 | 12.84 ms | 0 |
Memory
Fargate held 29.9% to 33.8% of a 1 GB task across the test window, roughly 306 to 346 MB, with CPU averaging 8.4% to 22.7% and spiking to 80.5%. Lambda peaked at 166 MB of 1024 MB allocated, on the busiest function. A single comparative percentage would mislead here, because one always-on provisioned task is not the same unit as seven functions billed on what they actually consume, so both are given as measured.
| Function | Peak | Average | Invocations |
|---|---|---|---|
| owners | 166 MB | 154.64 MB | 3,826 |
| pets | 162 MB | 151.15 MB | 2,338 |
| visits | 160 MB | 141.06 MB | 937 |
| vets | 159 MB | 142.90 MB | 1,421 |
| specialties | 148 MB | 132.07 MB | 476 |
| pettypes | 147 MB | 128.85 MB | 410 |
Cost
These figures are modeled, not billed. They apply published us-east-1 on-demand pricing to the resource shapes measured above. Excluded from both sides: the database, CloudWatch Logs and data transfer, since both architectures need them at comparable rates and they do not change the delta. Lambda's always-free tier is not applied, so the low tier is an upper bound.
The original model assumed 150 ms average Lambda execution. The measured average is 12.84 ms, so that assumption was 11.7x too high. Both versions are shown.
| Requests/month | Fargate + ALB | Lambda, 150 ms assumed | Lambda, 12.84 ms measured |
|---|---|---|---|
| 100,000 | ~$55 | ~$0.32 | ~$0.14 |
| 5,000,000 | ~$72 | ~$16 | ~$6.86 |
| 50,000,000 | ~$190 | ~$160 | ~$68.56 |
Fargate at 50M assumes scaling from 2 tasks to 4. That scaling is modeled, not measured: we did not load-test Fargate at these volumes, so there is no measured break-even point. Up to 50M a month, Lambda is cheaper in every tier.
Where the money actually goes
At measured duration, Lambda compute is only $0.17 per million requests. API Gateway is $1.00 per million, which is 73% of the total. Past a few million requests a month, the thing worth optimizing is not the compute, it is the gateway. Lambda Function URLs or an ALB in front of Lambda would cut most of it.
PetClinic is a demo app. For the same comparison on a production SaaS API, with the Lambda side measured under load in eu-central-1, see what a real SaaS API costs on Fargate vs. Lambda.
What this does not prove
- It is one reference application. PetClinic is a small CRUD API. Something with heavier per-request work, large in-memory caches, or long-held connections may behave differently.
- Two retained Lambda runs against one Fargate run. Enough to establish the shape, not enough to characterize run-to-run variance precisely.
- Nothing here measures developer experience, operational burden, or the cost of the migration itself, which are usually what actually decides the question.
Want this run against your application?
The tooling that produced every number here deploys both stacks, load-tests each, collects the CloudWatch metrics, and tears everything down. Every migration we take on ships with a benchmark like this one, measured on your code rather than ours.
Book a free assessment