Java Serverless on AWS Lambda · Capstone Case Studies

Capstone: Cut a Spring Boot Function's Cold Start from 8 Seconds

Plan and justify the changes that bring a Spring Boot Lambda function's cold start under one second.

Scenario

Example company: an insurer's quote API is a Spring Boot 3 application (Java 21) running on Lambda through the AWS Serverless Java Container behind API Gateway. Cold starts take 8 seconds; with spiky traffic, about 4% of requests hit one, and API Gateway's 29-second limit is fine but users abandon slow quotes. Warm p95 is 60 ms.

Requirements

  • p99 latency under 1.5 seconds including cold starts; warm performance unchanged.
  • No change to the API contract.
  • The team wants to keep Spring for its developer experience if possible.

Constraints

  • The app uses Spring Data JPA with PostgreSQL (through RDS Proxy), Jackson, Spring Validation and Actuator.
  • It generates quote reference numbers with Random initialised at startup.
  • Budget: modest; provisioned concurrency for all traffic is considered too expensive.

Deliverables

  1. A measurement plan: how you find where the 8 seconds go.
  2. The ordered list of changes you would make, with expected impact.
  3. How you make the app SnapStart-safe (uniqueness, connections, credentials).
  4. When you would consider native images or provisioned concurrency instead, and the trade-offs.
  5. How you verify the result and protect it from regressions.

Hints

  • Spring can tell you which beans take time to initialise.
  • SnapStart snapshots whatever exists at the end of initialisation, including your random generator and connection pool.
  • Priming moves first-request work into the snapshot.

Draft each deliverable below before revealing the model answer and rubric.

Further reading: Lambda SnapStart · Handling uniqueness with SnapStart

Deliverables

How do you measure where the cold start goes?

Model answer

  • REPORT lines: Init Duration versus first-request duration.
  • Spring's BufferingApplicationStartup (or spring.main.log-startup-info plus actuator startup endpoint in a test environment) to rank slow beans; JFR or -Xlog:class+load for class loading counts.
  • Traces for the first request to see lazy initialisation (Hibernate, Jackson modules, SDK clients).

Rubric

Look for measurement before changes, at both init and first-request level, and use of Spring's startup tooling.

List the changes in order, with expected impact.

Model answer

  1. SnapStart with priming in a CRaC beforeCheckpoint hook (invoke a representative quote calculation with stub data, serialize a response, touch JPA metamodel): typically the largest reduction (to well under a second).
  2. Remove unused auto-configurations (Actuator in production Lambda, unused data sources), enable lazy init for rarely used beans.
  3. Configure the SDK and HTTP clients explicitly; give the function enough memory (CPU) for restore and first request.
  4. Re-measure; only if still above target, consider a native image for this service or scheduled provisioned concurrency for peak hours.

Rubric

Strong answers start with the highest-impact, lowest-change option (SnapStart + priming), then trim, and re-measure between steps.

Make the app SnapStart-safe.

Model answer

  • Replace the startup-seeded Random with SecureRandom per request or UUID.randomUUID() for references, or re-seed in afterRestore.
  • Close the connection pool (or evict connections) in beforeCheckpoint and let it reconnect after restore; use RDS Proxy with IAM tokens generated after restore.
  • Avoid caching time-sensitive values (timestamps, temporary credentials) during init; refresh them in afterRestore.
  • Deploy through published versions and an alias (SnapStart does not apply to $LATEST).

Rubric

Look for all three classes of snapshot hazards: uniqueness, network connections, and time- or credential-dependent state.

When would you choose native images or provisioned concurrency instead?

Model answer

  • Native image (Spring AOT + GraalVM): lowest start-up and memory, but longer builds, reflection configuration for JPA and other libraries, and different debugging; worth it if SnapStart cannot reach the target or memory cost matters at scale.
  • Provisioned concurrency: predictable latency for a fixed number of environments; good for known peaks when scheduled, expensive if sized for spiky maxima. Note it cannot be combined with SnapStart on the same version.

Rubric

Good answers compare cost, complexity and team impact rather than declaring one option universally best.

Verify and protect against regressions.

Model answer

  • Load test with traffic shaped like production (bursts after idle) and record cold-start share, Restore Duration, p99 and errors.
  • CI check that deploys to a test alias and fails if Restore/Init Duration exceeds a budget; keep the priming hook covered by tests.
  • Dashboards with p99 by cold/warm, and alarms on p99.

Rubric

Look for an automated performance budget in the pipeline, not a one-off optimisation.

Open in the app to mark lessons complete, take notes and track your progress.