Blog

Your Spring Boot migration compiles. Will it survive the first traffic spike?

Generated version: 2 of 200 requests succeeded. Tuned version: 68 of 200.

That was the result when we sent 200 simultaneous cold requests to an AI-generated Quarkus migration on AWS, then repeated the test after two changes. The port came from the Quarkus project's Spring-to-Quarkus migration skill; we added only the Lambda packaging needed to deploy it. The tuning relieved a startup bottleneck. The remaining failures exposed the next constraint: the database connection budget.

A coding agent can port the app. That alone does not tell you that 30 simultaneous cold starts will drop requests, or that 200 will hit a wall at 68. Those answers require testing the deployed application and tracing what happens when it fails.

We do that work before you commit to a rollout: establish what works, find what breaks under load, and measure which changes are worth making.

Two small changes made startup more dependable

The tuned version differs from the generated one in two changes to how the app starts and manages connections. Both versions ran against the same small PostgreSQL database through RDS Proxy.

Across two bursts of 30 simultaneous cold requests, the original version completed 58 of 60 successfully. Three environments exceeded Lambda's initialization time limit; two requests ultimately failed. Logs from the first burst showed contention during startup.

The tuned version completed all 60 requests successfully, with no initialization timeouts. Its slowest successful response across the two bursts was about 2.3 seconds. The original version's slowest successful response took 11.6 seconds.

In these tests, the changes made startup faster and more dependable.

For a business, that distinction matters. A migration that usually starts quickly can still produce a poor experience during a deployment or traffic spike. Finding that behavior before rollout gives you a chance to fix it.

Warm performance alone would have hidden the problem

We also ran four five-minute load tests at 20 virtual users. Together, the two versions handled 44,940 requests with zero failures. Median response times were approximately 28 to 29 milliseconds, with p95 values around 39 to 42 milliseconds.

Their steady-load response times were essentially the same.

The original version accumulated more Lambda billed time in both rounds. The tuned version also used fewer pinned database connections. A full cost comparison also needs to include the database, proxy, logging, and other infrastructure.

A warm latency chart could have made these deployments look interchangeable. The startup tests showed why they were not.

A proxy did not remove the database ceiling

At 200 simultaneous cold requests, the tuned migration completed 68 requests successfully. Our independently developed native Quarkus port completed 68 as well.

The shared constraint was the database connection budget. The small PostgreSQL instance had 79 configured connections, with fewer available to the application. Proxy telemetry showed 68 database connections, all pinned to client sessions. Each pinned session ties up a database connection, reducing the proxy's ability to absorb a burst. Under those conditions, the proxy could not share those connections across the incoming demand.

Connecting directly to the database produced immediate connection failures. Going through the proxy generally turned the excess demand into waiting requests that eventually hit the API timeout. An additional connection-setting change did not resolve the pinning.

The original migration completed only two of the 200 requests. The tuned version got past the startup bottleneck and reached the database ceiling.

This is why framework selection and database capacity have to be evaluated together. The same connection ceiling affected our own port. Changing frameworks does not automatically change the capacity of the system behind them.

A larger instance raises the ceiling; our earlier run on a db.t4g.xlarge had 853 proxy connections. At on-demand us-east-1 prices, that instance costs about 17 times as much as this one, and with every connection pinned, you would be sizing the database for your worst burst rather than your typical traffic. Before paying for that, we would check whether the application needs that many connections. There are several ways past that ceiling. The right one depends on your traffic shape, response-time requirements, and budget.

Measure what the customer experiences

One test returned 132 HTTP 500 responses while Lambda's function-error metric remained zero. A function can return an application error without Lambda recording an invocation failure. Infrastructure health alone would have missed the customer's experience.

That is why we check the responses customers receive alongside the infrastructure metrics. A healthy-looking dashboard is little comfort when requests are failing.

Correctness and capacity need separate evidence

The final deployed write checks passed 90 of 90 checks across seven modules, comparing native Quarkus against native Spring, then their SnapStart counterparts. The checks covered the application's expected write behavior, including validation and error responses.

A burst of first writes after SnapStart restores told a different part of the story. Quarkus completed all 60 first-write requests across owners and pets. Spring completed all 30 owner requests and eight of 30 pet requests before connection exhaustion caused the remaining requests to fail. The connection totals and pool configuration suggest Spring consumed more connections per restored environment in this setup.

Both SnapStart deployments could write after restore. Their capacity under simultaneous restores differed. Treating those as separate findings gives a team something useful to act on.

What we bring to a migration

We help teams assess Spring Boot migrations, tune Quarkus deployments, and investigate Java performance and cloud costs. We connect the code changes to the behavior that determines whether a rollout succeeds:

  • Verify application behavior against the existing contract.
  • Measure startup, steady traffic, and concurrency limits on the target infrastructure.
  • Trace failures through application logs, database connections, and customer-visible responses.
  • Establish what improved, what remains constrained, and what the cost evidence actually supports.

You get a before-and-after assessment, the changes we recommend, and the remaining limits to account for before rollout.

About the test: an internal PetClinic benchmark, run in AWS us-east-1 on October 16, 2026, against a small PostgreSQL RDS instance. It is not a customer production case study or a guarantee of equivalent results elsewhere. The SnapStart comparison uses the second of two rounds; overlapping test activity affected the first. Results are specific to the tested deployments and workloads.

If your Spring Boot application is becoming expensive to run, difficult to scale, or a candidate for Quarkus, talk with us about an assessment. We can help you find the constraints before your customers do.