AWS Lambda SnapStart: Eliminate Java Cold Starts in Production
How AWS Lambda SnapStart uses Firecracker snapshots to deliver sub-second Java startup times. Complete guide: internals, setup, priming, and comparison with Provisioned Concurrency.
Updated on 11 March 2026
Java developers who rely on AWS Lambda know the feeling all too well: the first invocation of a function after a period of inactivity takes two, three, sometimes five seconds to respond. For an API serving real users, that’s a dealbreaker. This phenomenon — the cold start — is one of the biggest barriers to adopting Java in serverless production environments.
The JVM is an extraordinarily capable platform, but it comes at a price: startup is slow. Loading classes, initializing frameworks, establishing database connections, warming internal caches — it all adds up. Throw Spring Boot on top, and initialization times can easily exceed three seconds on a standard Lambda function. Python or Node.js start up in a few dozen milliseconds under the same conditions.
AWS Lambda SnapStart, announced at re:Invent 2022, tackles this problem head-on. By leveraging a snapshot mechanism at the microVM level, it makes sub-second startup times achievable for Java functions — at no extra cost, and typically without any code changes. This guide takes a deep dive into how it works, what precautions to take, advanced optimization techniques, and when SnapStart is (or isn’t) the right tool.
Why Java Cold Starts Hurt So Much
To appreciate why SnapStart is such a meaningful step forward, you first need to understand exactly what happens during a classic Lambda cold start. The sequence involves several distinct phases, each adding its own latency.
The first phase is provisioning the execution environment. AWS Lambda must allocate a Firecracker microVM, load the base operating system, and prepare the isolated runtime for your function. This step is entirely managed by AWS and is already heavily optimized — it accounts for only a minor fraction of total time on a well-configured Java function.
The second phase — by far the longest — is JVM initialization and application code startup. The JVM must boot, the classloader must load and verify all required bytecodes, the JIT compiler must compile hot paths, and finally your initialization code runs (static blocks, Spring bean constructors, connection pool creation, configuration loading). Spring Boot alone can take two to four seconds here.
The third phase is processing the actual request. This part is identical between a cold start and a warm invocation: it’s your pure business logic.
In production, cold starts occur in three main scenarios: after a period of inactivity (Lambda recycles idle environments after roughly 15 minutes), during rapid scale-out events that force Lambda to spin up multiple new instances in parallel, and during deployments that replace existing instances. In a microservices architecture with dozens of Java Lambda functions, these latency spikes can cascade and significantly degrade the user experience.
How severe the problem is also depends on your traffic pattern. A function invoked thousands of times per second continuously will stay warm permanently. But a business API used during office hours, with quiet nights and weekends, will hit cold starts regularly and visibly. That profile — common in SMB and enterprise contexts — is exactly what SnapStart is designed for.
How AWS Lambda SnapStart Works: The Firecracker Snapshot Mechanism
SnapStart is built on an elegant idea: since Java initialization is slow, run it once and capture the result as a snapshot that can be reused on every cold start. The concept isn’t new in computing, but its implementation in Lambda is remarkably well integrated.
In practice, when you enable SnapStart and publish a version of your Lambda function, AWS doesn’t just record your code. It fully executes your function’s initialization phase — JVM startup, class loading, your initialization handler — and then takes a complete snapshot of the Firecracker microVM running that code. The snapshot captures the memory and disk state of the execution environment at the moment initialization completes. The snapshot is then encrypted and intelligently cached by AWS to minimize retrieval latency.
When a cold start occurs later, Lambda no longer starts from scratch. Instead of restarting the JVM and re-running your initialization code, it restores the environment from the snapshot. The JVM is already running, your classes are already loaded, your Spring beans are already initialized. Restoration typically takes a few dozen milliseconds, compared to the several seconds that classic initialization required. According to AWS’s official documentation, SnapStart can reduce startup time by up to 10× compared to a standard Java Lambda function, with sub-second latency achievable even for heavy Spring Boot applications.
The underlying technology is Firecracker, the open-source microVM technology developed by AWS that powers both Lambda and AWS Fargate. Firecracker has had native VM snapshot and restore capability since its earliest versions — SnapStart exposes that capability at the application level.
The protocol that orchestrates all of this is called CRaC (Coordinated Restore at Checkpoint). CRaC is an OpenJDK extension that defines a standard API allowing application code to register as a resource to be notified before the checkpoint and after restoration. AWS integrated a custom CRaC context into its managed Java Lambda runtime, which lets SnapStart work without code changes in the vast majority of cases — while also enabling advanced optimizations when needed.
Supported Runtimes and Compatibility
SnapStart launched in November 2022 with Java 11 support (Amazon Corretto 11), and was later extended to Java 17 and Java 21 as they became available. AWS officially announced Java 21 support on Lambda in November 2023, with SnapStart available from day one. Java 21 brings Virtual Threads (Project Loom), Record Patterns, JIT performance improvements, and a reduced memory footprint — benefits that stack on top of SnapStart’s gains.
In July 2024, AWS extended SnapStart to Lambda functions running on ARM64 architecture (Graviton2 and Graviton3 processors). This combination — SnapStart + Graviton — is now the most cost-effective and performant configuration for Java on Lambda. The per-millisecond cost is lower on ARM64, and SnapStart gains apply regardless of the underlying architecture.
Beyond Java, SnapStart has also been extended to other runtimes such as Python and .NET, but the impact is most dramatic for Java. The JVM has an inherently high startup cost — whereas Python starts in under 100ms without SnapStart, a Java application can take 10 to 30 times longer. SnapStart doesn’t deliver the same magnitude of improvement on runtimes that already start quickly.
It’s important to note that SnapStart only works on published function versions. The special $LATEST alias is not supported. This means you’ll need to adopt a versioning and alias workflow in your deployments — which is a production best practice regardless. SnapStart is also available across all major AWS regions with no geographical restrictions.
Essential Precautions: Managing Uniqueness After Restore
The SnapStart promise is compelling, but it introduces a new challenge that every developer must understand before enabling it in production: the uniqueness problem. When Lambda restores multiple instances from the same snapshot, they all share exactly the same initial state. If your code generates unique identifiers, random seeds, or secrets during initialization, every restored instance will start with identical values — which can be a security vulnerability or a source of collisions.
AWS’s official Lambda documentation is explicit on this point: to maintain uniqueness with SnapStart, you must generate any unique content after the initialization phase — that is, inside your invocation handler, not in constructors or static initializers.
Concrete cases to watch for include UUIDs generated at init and used as session or transaction IDs, random number generators seeded with a fixed value, timestamps captured during initialization, and TLS connections whose certificates and session keys are established at init. For cryptographic generators backed by /dev/urandom or SecureRandom, Lambda automatically restores entropy — that case is handled natively.
The other classic problem is network connections. TCP connections (to an RDS database, an ElastiCache cluster, a DynamoDB endpoint) established during initialization will be embedded in the snapshot, but they may be expired or invalid by the time the snapshot is restored. You must implement reconnection logic in the restore hook, or use lazy initialization patterns for connections.
For both issues — uniqueness and connections — the clean solution is to use CRaC hooks. By implementing the Resource interface from the org.crac package, you can register callbacks that execute automatically just before the snapshot is taken (beforeCheckpoint) and just after restoration (afterRestore). The beforeCheckpoint hook is used to cleanly close connections before capture. The afterRestore hook reopens them, regenerates entropy, and resets anything that needs to be unique per instance.
import org.crac.Context;
import org.crac.Core;
import org.crac.Resource;
public class DatabaseConnectionManager implements Resource {
private DataSource dataSource;
public DatabaseConnectionManager() {
Core.getGlobalContext().register(this);
this.dataSource = createDataSource();
}
@Override
public void beforeCheckpoint(Context<? extends Resource> context) {
// Close connections before snapshot
dataSource.close();
}
@Override
public void afterRestore(Context<? extends Resource> context) {
// Re-establish connections after restore
this.dataSource = createDataSource();
}
}
Modern Java frameworks are increasingly handling these scenarios automatically. Spring Boot 3.2+ includes native CRaC support. Micronaut, Quarkus, and Helidon have all integrated SnapStart adapters that manage connections and resources transparently. If you’re using one of these frameworks in a recent version, the majority of precautions are already taken care of.
Priming: Getting the Most Out of SnapStart
SnapStart solves the initialization cost problem, but it doesn’t guarantee minimal latency on its own. There’s a complementary technique called priming that pushes performance even further, by forcing operations that are normally deferred to execute during the initialization phase — before the snapshot is captured.
The principle is straightforward: the more prepared state is baked into the snapshot, the less work remains at restore time. Modern JVMs use Just-In-Time compilation techniques that compile bytecode into native code as hot paths are detected. Without priming, the first few calls after a restore can be slightly slower than subsequent ones, as the JIT re-compiles critical paths.
Priming means making dummy calls to your application’s critical paths inside the beforeCheckpoint hook, so the JIT has already compiled those paths before the snapshot is taken. When the function is restored, compiled native code is already in memory — the first real invocation is just as fast as all the ones that follow.
AWS documents this technique in detail with concrete examples including Spring Boot. The post shows how to perform priming DynamoDB calls, warm HTTP connections, or pre-load configuration data from external services. Priming gains stack on top of SnapStart’s baseline and can reduce restore latency to a few dozen milliseconds on complex applications.
For Spring Boot applications, the most effective priming approach is to trigger a full invocation of the application context inside beforeCheckpoint. You can use Spring Boot’s ApplicationRunner interface to run this warm-up automatically at startup, before CRaC captures the snapshot. The AWS Open Source blog post on Sonar’s experience with Micronaut illustrates this approach in real production, with measurable results on latency-sensitive APIs.
Priming does have one constraint: everything “warmed up” in the snapshot must remain valid at restore time. You can’t pre-open database connections and expect them to still be alive hours later. Priming therefore applies primarily to purely local operations (JIT compilation, class loading, data structure pre-allocation) and to network configuration calls that produce stable, cacheable data.
SnapStart vs Provisioned Concurrency: Choosing the Right Strategy
Before SnapStart, the standard answer to Lambda cold start problems was Provisioned Concurrency. Both features address the same problem but through radically different approaches, and they are mutually exclusive on a given function version — you cannot enable both simultaneously.
Provisioned Concurrency works by keeping a predefined number of Lambda environments already initialized and ready to handle invocations. There is technically no cold start for those instances: they’re always warm. The trade-off is that you pay for those environments continuously, even when your traffic is low or zero. Billing applies to the total duration of provisioned availability, regardless of actual invocation count.
SnapStart works differently: it dramatically reduces cold start duration (from several seconds to a few dozen milliseconds), but a cold start still occurs for each new instance. The additional cost is zero — you pay only for standard invocations, with no charge for snapshot restore time.
The right choice depends on your traffic profile and latency requirements. For applications with unpredictable or highly variable traffic — periodic spikes, business-hours usage, ad hoc marketing campaigns — SnapStart is generally the better option. Provisioned Concurrency would be underutilized most of the time, generating costs with no real benefit. SnapStart ensures good performance during spikes at zero standing cost.
For applications with consistently high traffic where the maximum acceptable latency is very tight (single-digit milliseconds) and budget allows, Provisioned Concurrency remains relevant. It guarantees zero-millisecond startup for provisioned instances, whereas SnapStart will always add a few dozen milliseconds of restore overhead. For financial APIs or real-time payment systems with strict SLAs, that difference can be meaningful.
A hybrid strategy often adopted in practice is to use SnapStart as a baseline defense against cold starts, combined with scheduled CloudWatch Events to keep a handful of instances warm during peak hours. This approach requires no Provisioned Concurrency and remains cost-effective. For a broader view on cost control, the article on 7 levers to optimize your AWS costs covers complementary strategies.
Configuring SnapStart: SAM, CDK, Terraform, and the Console
Enabling SnapStart is remarkably straightforward. Regardless of your deployment method, configuration comes down to two elements: enabling the SnapStart option on your function, and publishing a version (since SnapStart only applies to published versions, not $LATEST).
With AWS SAM, the configuration is concise:
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: java21
Handler: com.example.Handler::handleRequest
SnapStart:
ApplyOn: PublishedVersions
AutoPublishAlias: live
The AutoPublishAlias option ensures that every SAM deployment publishes a new version and updates the live alias. Your consumers (API Gateway, EventBridge, etc.) should point to this alias rather than directly to the function.
With AWS CDK (TypeScript):
const fn = new lambda.Function(this, 'MyFunction', {
runtime: lambda.Runtime.JAVA_21,
handler: 'com.example.Handler::handleRequest',
code: lambda.Code.fromAsset('target/function.jar'),
snapStart: lambda.SnapStartConf.ON_PUBLISHED_VERSIONS,
});
const version = fn.currentVersion;
new lambda.Alias(this, 'LiveAlias', {
aliasName: 'live',
version,
});
With Terraform:
resource "aws_lambda_function" "my_function" {
function_name = "my-java-function"
runtime = "java21"
handler = "com.example.Handler::handleRequest"
snap_start {
apply_on = "PublishedVersions"
}
}
resource "aws_lambda_alias" "live" {
name = "live"
function_name = aws_lambda_function.my_function.function_name
function_version = aws_lambda_function.my_function.version
}
In all cases, note that the first deployment with SnapStart enabled will take slightly longer than a standard deploy. AWS must run the full initialization and create the snapshot before marking the version as deployed. This is a one-time cost per version: every subsequent cold start benefits from it.
Once deployed, you can monitor your SnapStart restore performance in Amazon CloudWatch via the InitDuration metric, which measures the duration of the snapshot restoration phase. A value below 200ms generally indicates a healthy configuration. The Duration metric measures your pure execution time independently.
Use Cases and Real-World Production Feedback
Serverless architecture is particularly attractive for companies that want to focus on their core business rather than managing infrastructure. The article on serverless AWS for SMBs explores this context in depth. SnapStart removes the main technical barrier that prevented Java from fitting naturally into these architectures.
In practice, the use cases that benefit most from SnapStart are REST APIs exposed to end users via API Gateway or Lambda Function URLs. These APIs have latency constraints that are directly visible and measurable by users. A 3-second cold start on a login or product search API directly degrades the user experience.
Orchestration microservices — Lambda functions that aggregate calls to other services, validate data, apply business rules — are also strong candidates. These functions tend to be the heaviest in terms of code and dependencies (Spring Data, AWS SDK, HTTP clients, JSON parsers), and are therefore the most affected by cold starts.
Event-driven processing pipelines consuming SQS queues or Kinesis streams can also benefit from SnapStart during scale-out events. When traffic spikes suddenly, Lambda creates several instances in parallel — without SnapStart, each one goes through a full cold start, delaying queue processing.
Sonar’s production case study, published by AWS on its Open Source blog, illustrates the real-world benefits clearly. By migrating to SnapStart with Micronaut on their Lambda infrastructure, they achieved significant performance improvements on their internal APIs — with an implementation that required minimal changes to their existing codebase. That type of outcome is representative of what you can realistically expect from well-configured Micronaut and Spring Boot applications.
SnapStart and the Modern Java Ecosystem: GraalVM, Quarkus, Micronaut
It’s a legitimate question whether SnapStart competes with or complements other approaches to reducing Java cold starts, most notably ahead-of-time compilation with GraalVM Native Image.
GraalVM Native Image compiles your Java application into a self-contained native binary, with no JVM involved. Startup times are spectacular — often under 50ms even for Spring Boot applications. Memory footprint is also reduced. But native compilation comes with its own constraints: it requires extensive configuration for reflection-heavy features (heavily used in Spring), significantly increases build times (by several minutes), and can introduce behavioral differences between JVM mode and native mode.
SnapStart offers a pragmatic alternative: it requires no changes to the build process, works with any existing Java code, and delivers sub-second results that are sufficient for the vast majority of use cases. For a team that already knows Spring Boot and simply wants to improve cold starts without investing in native compilation, SnapStart is the path of least resistance.
Quarkus and Micronaut occupy an interesting middle ground: their startup times are naturally shorter than Spring Boot’s (thanks to compile-time rather than runtime dependency injection), and both have excellent support for SnapStart AND GraalVM. For a new Java Lambda application, Micronaut is often the recommended baseline framework with SnapStart enabled — Sonar’s production experience is a solid illustration of this.
For the 10 AWS Lambda use cases we cover in our dedicated article, SnapStart is most relevant for functions exposing synchronous APIs, and less critical for asynchronous background processing where startup latency is invisible to end users.
Selecting the right Java runtime also ties into the AWS Well-Architected Framework, specifically the Performance Efficiency pillar, which recommends evaluating managed features like SnapStart before implementing complex application-level optimizations. Our article on the Well-Architected Framework explores those principles as they apply to SMBs.
Monitoring and Measuring Gains with CloudWatch
Enabling SnapStart without measuring its impact is like deploying blind. AWS CloudWatch provides the metrics needed to accurately assess gains and detect potential issues.
The key metric is InitDuration, which measures the duration of the initialization or restore phase before request processing. Before SnapStart, this value represents your full cold start (JVM + application init). After enabling it, it represents only the snapshot restore time — this number should drop dramatically.
For an objective before/after comparison, create two CloudWatch alarms: one on the P99 of InitDuration (the 99th percentile, representative of worst-case behavior) and one on the P50 (median). The gap between the two tells you how variable your restores are. High variability may indicate that the snapshot is occasionally evicted from AWS’s cache and needs to be reloaded from storage.
The Duration metric (pure handler execution time) should not change with SnapStart. If it increases, that’s a signal that some deferred initializations are now happening inside the handler rather than at init — a pattern to fix using afterRestore hooks.
For deeper observability, AWS X-Ray natively traces the Initialization and Invocation phases separately, allowing you to visualize the exact time breakdown for every invocation, cold or warm. This integration requires no additional configuration in AWS Lambda’s managed Java runtimes.
The combination of SnapStart and a solid CloudWatch monitoring strategy ties directly into the serverless architecture principles defined in the Well-Architected Framework. The Reliability pillar recommends instrumenting and measuring before optimizing. SnapStart without monitoring is an optimization you can’t prove.
Conclusion
AWS Lambda SnapStart is a major step forward for teams that want to keep Java — with its rich ecosystem — in serverless architectures without sacrificing responsiveness. By leveraging the snapshot capability of Firecracker microVMs through the CRaC protocol, AWS has solved the structural problem of Java cold starts in an elegant, transparent, and cost-free way.
The implementation is accessible: a few lines of configuration in your SAM template or CDK code, and your next published version automatically benefits from sub-second startup times. The cases where code changes are needed — handling uniqueness, reconnecting in CRaC hooks — are well documented and natively managed by modern frameworks like Spring Boot 3.2+, Micronaut, and Quarkus.
For teams whose Java Lambda functions suffer from cold starts that are visible to users, SnapStart is the first action to take today — before considering GraalVM compilation or Provisioned Concurrency. The effort-to-benefit ratio is excellent.
If you manage an AWS infrastructure with Java Lambda functions in production and would like an audit of your SnapStart configuration, an analysis of your CloudWatch metrics, or guidance migrating to Java 21 with Graviton, our team of certified AWS experts is available for an initial discussion.
Frequently asked questions
- Which Java runtimes are compatible with AWS Lambda SnapStart?
- SnapStart supports Java 11 (Amazon Corretto 11), Java 17, and Java 21 on AWS Lambda — on both x86_64 and ARM64 (Graviton) architectures. It does not apply to Python or Node.js runtimes, where cold start times are already low.
- Is SnapStart free on AWS Lambda?
- Yes, SnapStart carries no additional charge. You pay only for standard Lambda invocation costs. There is no fee for snapshot creation, storage, or restoration time.
- Can you use SnapStart together with Provisioned Concurrency?
- No. SnapStart and Provisioned Concurrency are mutually exclusive on a given function version. You must choose one strategy or the other. For most variable-traffic workloads, SnapStart delivers better cost-efficiency.
- What are the key limitations of SnapStart to know before adopting it?
- The main limitations are: SnapStart only works on published function versions (not $LATEST), it introduces a uniqueness problem for IDs or random seeds generated at initialization, and network connections established during init may be stale after restoration. CRaC hooks solve both of the latter issues.
- How do you enable SnapStart in AWS SAM or Terraform?
- In SAM, add 'SnapStart: ApplyOn: PublishedVersions' to your function properties and use 'AutoPublishAlias' to ensure a version is published on every deploy. In Terraform, add a 'snap_start { apply_on = "PublishedVersions" }' block to your aws_lambda_function resource, then create an aws_lambda_alias pointing to the published version.
- What is 'priming' and why does it matter with SnapStart?
- Priming means executing the critical hot paths of your application inside the 'beforeCheckpoint' CRaC hook, so the JIT compiler has already compiled those paths before the snapshot is captured. When the function is restored, compiled native code is already in memory, making the first real invocation as fast as subsequent warm ones.
Related Articles
AWS Lambda: 10 concrete use cases to automate your business
Discover 10 practical AWS Lambda use cases to automate your business processes without managing servers.
Serverless on AWS: why SMBs are adopting it
How serverless architecture on AWS helps SMBs reduce infrastructure costs and focus on their core business.
Renaissance Developer: Werner Vogels' Framework for Thriving in the AI Era
Werner Vogels presented the 5 qualities of the Renaissance Developer in his final re:Invent keynote. Analysis and practical implications for tech teams.
AWS Raised Prices 15%? No, It's More Complicated Than That
Unpacking the AWS EC2 Capacity Blocks pricing adjustment: why alarmist headlines miss the point about dynamic pricing in cloud computing.
DORA 2025: AI Amplifies Your Strengths (and Your Weaknesses)
Analysis of the DORA 2025 report on AI in software development. 5,000 professionals surveyed reveal that AI is an amplifier, not a silver bullet.
Coding 10x faster with AI: the new calculus of agentic development
When a team produces code 10 times faster with AI, everything else must keep up: testing, deployment, coordination. Lessons from an Amazon Bedrock team.