What it means
The JVM manages its own heap inside the container Lambda gives it, and it decides how large that heap may be from the memory it can see at startup. When an allocation cannot be satisfied and garbage collection cannot free enough to satisfy it, the JVM throws java.lang.OutOfMemoryError — an Error, not an Exception, because it signals a condition the program is not expected to recover from.
Unlike a container OOM kill, this failure is polite. You get a full stack trace naming the frame that was allocating, which is usually enough to identify the offending collection or parser on the first read. That is a real advantage, and it is worth deliberately keeping: a JVM configured to fail at a heap limit below the container size gives you a stack trace, where a JVM allowed to grow into the container gets SIGKILL and leaves nothing behind.
The part that catches people out on Lambda specifically is the JVM's fixed overhead. A Java function is not paying only for your objects — it is paying for the runtime, the loaded classes, thread stacks, metaspace, and whatever framework you brought. That baseline is substantial before your handler allocates its first byte, which is why Java functions on 128 MB or 256 MB fail in ways the same code never does on a laptop, and why the answer here is so often "raise the memory" rather than "find the leak".
Read the area named in the error before changing anything. Java heap space and Metaspace are different regions with different limits, and raising the object heap does nothing at all for a metaspace exhaustion. GC overhead limit exceeded is a third shape again: the heap is not quite full, but collection is consuming almost all the CPU and reclaiming almost nothing, which means something is being retained that you believe is being released. Each of those points at a different fix, and the only thing that tells them apart is the second half of the error message.
CloudWatch’s Errors metric: The error escaped your handler, so Lambda reports the invocation as failed and CloudWatch’s Errors metric counts it. An OutOfMemoryError thrown out of the handler fails the invocation, so CloudWatch counts it. LogStitch extracted the type OutOfMemoryError from the example below but classifies it as unclassified rather than uncaught, because a caught OutOfMemoryError — rare, but legal Java — would look identical in the log line alone.
What it looks like in CloudWatch
This is the shape the failure arrives in: the lines of one invocation scattered among everything else the log group received at the same moment.
CloudWatch Logs Before
10 raw lines, in the order CloudWatch delivered them.
LogStitch After
The same lines, grouped into the invocation they belong to.
ErrorMemory ceiling6d1e9f22-8b40-4c73-a5e1-9f3b2c8d7e41
- 08:02:44.117PLAT
START RequestId: 6d1e9f22-8b40-4c73-a5e1-9f3b2c8d7e41 Version: $LATEST
- 08:02:44.310INFO
INFO Reconciling statements month=2026-07
- 08:02:45.884INFO
INFO Scan returned 118402 items
- 08:02:47.201
java.lang.OutOfMemoryError: Java heap space
- 08:02:47.201
at java.base/java.util.Arrays.copyOf(Arrays.java:3512)
- 08:02:47.201
at java.base/java.util.ArrayList.grow(ArrayList.java:237) at com.example.StatementReconciler.collect(StatementReconciler.java:88) at com.example.Handler.handleRequest(Handler.java:41)
3 lines - 08:02:47.560PLAT
END RequestId: 6d1e9f22-8b40-4c73-a5e1-9f3b2c8d7e41
- 08:02:47.560PLAT
REPORT RequestId: 6d1e9f22-8b40-4c73-a5e1-9f3b2c8d7e41 Duration: 3443.28 ms Billed Duration: 3444 ms Memory Size: 512 MB Max Memory Used: 512 MB Status: error
The panel on the right is generated by running the excerpt on the left through the same parser that powers the free web stitcher — it is what the tool actually produces for this input, not an illustration of it.
How to confirm it from the logs
The OutOfMemoryError line names which memory area was exhausted, and the area matters more than the error does. Java heap space means ordinary object allocation failed. Metaspace means class metadata filled — usually far too many classes loaded, or a framework generating proxies. GC overhead limit exceeded means collection was running almost continuously and reclaiming almost nothing, which is the slow-leak signature.
Cross-check the REPORT line. If Max Memory Used equals Memory Size, the container was full too. If it is meaningfully lower, the JVM's own heap sizing was the constraint and the container still had room — which points at heap configuration rather than at the allocation.
Causes, most likely first
The allocation is too small for the JVM's baseline plus the workload
Check Memory Size against Max Memory Used on successful invocations. A JVM carries a substantial fixed cost before your code allocates anything — the runtime itself, loaded classes, and the framework. If successful runs already sit high, there was never much room for the actual request.
A query or file is materialised entirely in memory
Read the stack trace under the error, which names the frame that was allocating when the heap ran out. A collection class, an ORM result list, or a document parser near the top of that trace means the whole result was being built in memory rather than streamed.
Static state accumulates across warm invocations
Compare cold-start invocations against ones later in a container's life. Static fields and singletons live for the lifetime of the execution environment, not the invocation, so a static cache or list keeps growing across every request the container serves.
Class metadata rather than object data has filled
Read the area named in the error. Metaspace is not the object heap — raising the object heap will not help. It fills when an unusual number of classes are loaded, which on Lambda usually means a large dependency-injection framework or heavy runtime proxy generation.
Fixes
Raise MemorySize so the JVM has room to size its heap
This is the first thing to try, because the JVM's default heap is derived from the memory it can see. Java functions are generally unhappy below 512 MB, and 1024 MB or more is a reasonable starting point for anything using a framework. CPU scales with memory too, which also shortens the JVM's already-slow cold start.
yamlResources:
OrderFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: java21
MemorySize: 1024 # was 512
Timeout: 30
Set heap bounds explicitly rather than leaving them implicit
Leaving the JVM to infer its heap from the container works until it does not. Setting the maximum explicitly, below the container size, leaves room for the JVM's non-heap overhead and makes the failure a catchable OutOfMemoryError with a stack trace rather than a container OOM kill with nothing at all.
yamlEnvironment:
Variables:
# Leave headroom under a 1024 MB allocation for metaspace,
# thread stacks, and the JVM's own overhead.
JAVA_TOOL_OPTIONS: "-Xmx768m -XX:MaxMetaspaceSize=128m"
Stream results instead of collecting them
Where the stack trace points at a collection being filled, the fix is to consume records as they arrive rather than gathering them first. Most AWS SDK v2 clients offer a paginator that fetches lazily, so only one page is resident at a time.
java// Before: every page collected before anything is processed.
// List<Item> all = client.scanPaginator(request).items().stream().toList();
// After: one page resident at a time.
client.scanPaginator(request).items().forEach(item -> process(item));
Bound anything static
Static fields outlive the invocation and live as long as the execution environment. If a static map or list is used as a cache, give it a maximum size and an eviction policy, or move it inside the handler so it is collected when the invocation ends.
Also seen as
The same underlying failure, worded differently by a different runtime, SDK version, or logging layer. All of these land here — there is no separate page for each phrasing.
Related errors
Errors that show up alongside this one, or that people mistake for it.
References
- AWS Lambda Developer Guide — Building Lambda functions with Java
- AWS Lambda Developer Guide — Configuring function memory
LogStitch finds this automatically, across every invocation in your account.
Paste a log excerpt into the free web stitcher and see it grouped, classified, and measured in your browser — nothing is uploaded. Or run the Mac app against your own AWS profiles and get the same view over every function you own.