Skip to content
Ameen Abozaid
PublishedBEG-038-L04

OutOfMemoryError and StackOverflowError

  • Lesson 4 of 5
  • 10 min read
  • Beginner
On This Lesson 11 topics

Lesson Overview

The two memory areas from this module can each fill up. When the stack fills up, Java throws a StackOverflowError. When the heap cannot hold any more objects, Java throws an OutOfMemoryError. Both are rare in tidy programs, and both are common in the first months of learning, in logs, and in interviews.

This is a short lesson. The goal is to recognise each error, link it to the picture from lesson 1, and know what to do first. Tuning memory sizes and hunting leaks with tools come in later levels.

You will use three ideas you already have: the call stack and recursion (BEG-014), reachability (lesson 3), and the exception hierarchy (BEG-029), where you saw that Error is a different branch from Exception.

Learning Objectives

By the end of this lesson you will be able to:

  • Explain what causes a StackOverflowError and how to prevent the usual cause.
  • Explain what causes an OutOfMemoryError and relate it to reachable objects.
  • Say where both errors sit in the exception hierarchy and why normal code should not catch them.
  • Match a short snippet or a symptom to the correct error.
  • Name the first thing to check for each error.
  • Tell the two errors apart from a failure that is only a slow program.

Written Explanation

StackOverflowError: the stack ran out

Every method call adds a frame to the stack, and the stack has a limited size. If methods call each other so deeply that there is no room for another frame, the JVM throws StackOverflowError. The Java API says it is thrown when a stack overflow occurs because an application recurses too deeply.

The usual cause is recursion with no stopping condition, or with a stopping condition that is never reached:

java
static void loop() { loop(); }   // every call adds a frame; none ever returns

BEG-014 taught that a recursive method needs a base case: a branch that returns without calling itself, and that each recursive call must move toward it. Missing or wrong base cases are by far the most common reason you will meet this error.

Two important notes:

  • A recursion with a correct base case can still overflow if the input is very deep (a million nested calls). Loops do not have this limit, so a loop is often the better tool for very large counts. The exact limit depends on the JVM and its settings; do not rely on a particular number.
  • The stack trace of a StackOverflowError is long and repeats the same few lines. Read the repeated lines: they name the methods that keep calling each other. (A long repeating trace is often the only clue to which two methods form the loop.)

OutOfMemoryError: the heap ran out

When the JVM cannot allocate an object because it is out of memory, and the garbage collector cannot free enough, it throws OutOfMemoryError. The official wording matters: the collector is tried first. The error means that even after reclaiming everything unreachable, there is not enough space.

So the heap is full of reachable objects. Two situations produce this:

  1. A leak: you keep references to objects you do not need, so the collector cannot reclaim them (lesson 3, the HISTORY list).
  2. A genuinely large job: you load more data than the configured heap can hold, for example reading a huge file entirely into a list.

The message after the error name usually says which area was exhausted. Java heap space is the common one for ordinary object allocation. The JVM has other memory areas, so other messages exist; you will meet them later.

They are Errors

Both classes extend VirtualMachineError, which extends Error, not Exception. Error is the branch for serious problems that applications normally should not try to catch (the usual convention, see BEG-029). In practice:

  • Do not write catch (Throwable t) or catch (Error e) to hide them. At that point the program may be in an unreliable state.
  • The right response is to fix the cause, not to handle the symptom.
  • There are rare legitimate places to catch them, for example a top-level handler that logs and shuts down cleanly. We catch them in this lesson's demos only so that the demo can print a result.

What to do first

SymptomFirst checks
StackOverflowErrorRead the repeating lines of the trace. Check the base case. Check that the argument moves toward it. Consider a loop.
OutOfMemoryError: Java heap spaceAsk what is growing: a static list, a cache, results loaded all at once. Reduce what you keep or load in pieces. Raising the heap size is a last resort, and you should do it only after understanding why it is full.

The JVM has an option to set the maximum heap size (-Xmx, for example); the details of tuning, and diagnosing leaks with tools, are covered in later levels, so we only note that the option exists.

Examples

Example 1: a stack overflow from a missing base case (simple)

java
static int bad(int n) { return n * bad(n - 1); }   // no base case
static long fact(int n) { if (n <= 1) return 1; return n * fact(n - 1); }

System.out.println(fact(5));
try { bad(5); } catch (StackOverflowError e) { System.out.println("SO"); }

Output:

text
120
SO

bad(5) calls bad(4), bad(3), and on to zero and negative numbers; nothing ever stops it, so the frames pile up until the stack is full. The fixed version, fact, returns directly when n <= 1, so the chain ends.

Example 2: depth is fine, until it is not (practical)

java
static int depth = 0;
static void loop() { depth++; loop(); }
static int countdown(int n) { return n == 0 ? 0 : 1 + countdown(n - 1); }

System.out.println(countdown(1000));
try { loop(); } catch (StackOverflowError e) {
    System.out.println("overflow, deep? " + (depth > 1000) + " " + e.getClass().getName());
}

Output:

text
1000
overflow, deep? true java.lang.StackOverflowError

A recursion 1,000 calls deep works. The endless one gets past 1,000 and still overflows. Note that I print depth > 1000 instead of the exact depth, because the exact number depends on the JVM version, its settings, and the frame size, and is not worth memorising.

Example 3: filling the heap with reachable objects (realistic)

java
List<long[]> keep = new ArrayList<>();
try {
    while (true) keep.add(new long[1_000_000]);   // each is about 8 MB
} catch (OutOfMemoryError e) {
    int n = keep.size();
    keep = null;                                  // release them before printing
    System.out.println("OOM: " + e.getMessage() + " " + (n > 0));
}

I ran this with the heap limited to 32 MB (java -Xmx32m) so that it fails quickly. Output:

text
OOM: Java heap space true

Each new long[1_000_000] holds one million 8-byte values, a little over 8 MB. Every array is reachable through keep, so garbage collection cannot help, and after a few arrays the limit is reached. The keep = null line makes the arrays unreachable again so that the println itself has memory to work with.

Production code would not do this; the pattern is what happens when a real program keeps accumulating data, such as a list of every request ever received.

Backend Insight

In a long-running service, these errors are alarms:

  • A StackOverflowError in a request is usually a code bug (a missing base case, or two methods that call each other), or a very deep structure such as a cyclic object graph being walked naively. It affects the request that triggered it and shows up in logs with a long repeating trace.
  • An OutOfMemoryError is more serious. Memory use that grows slowly over days and then ends in an OutOfMemoryError points to a leak. Memory that spikes with one huge request points to unbounded input, for example loading a whole file or query result at once.

Operations teams usually monitor heap usage and restart services that die, but a restart hides the problem rather than fixing it. This is why the roadmap lists memory diagnosis as a later skill (MID-008). What you need now is the vocabulary: stack errors come from call depth; heap errors come from reachable data.

Never "fix" an OutOfMemoryError by catching it and continuing. After one, other objects may also fail to allocate, and the program can no longer be trusted to behave correctly.

Common Mistakes

Mistake 1: treating them as ordinary exceptions

What goes wrong: you catch Throwable around a loop and carry on.

Fix: let them end the request or the program, log them, and fix the cause.

Mistake 2: blaming the heap for a stack problem (or the reverse)

What goes wrong: you increase the heap size for a StackOverflowError, which does nothing; or you add a base case for an OutOfMemoryError.

Fix: read the error name. Stack = call depth. Heap = reachable objects.

Mistake 3: believing a correct recursion can never overflow

What goes wrong: a correct recursive method on very large input crashes.

Fix: use a loop for very large counts, or limit the input size.

Mistake 4: "more memory fixes it"

What goes wrong: increasing the limit delays the error from a leak by a few hours.

Fix: find what keeps growing.

Exercises

Level A: Foundation

  1. Which error is thrown when a method calls itself without ever stopping?
  2. Which memory area is full when you get OutOfMemoryError: Java heap space?
  3. True or false: OutOfMemoryError is a subclass of Exception.

Level B: Application

  1. The method below should add up 1 + 2 + ... + n recursively. It throws StackOverflowError. Fix it.
java
static int sum(int n) { return n + sum(n - 1); }
  1. A web service runs for a week, and its heap usage rises steadily until it throws OutOfMemoryError, even though traffic is constant. Name two things you would check first.
  2. Decide the error for each situation: (a) two methods call each other with no exit; (b) a program that adds every incoming message to a static list forever; (c) reading a 20 GB file into one String on a 1 GB heap.

Level C: Challenge

  1. A teammate "fixes" an OutOfMemoryError by wrapping the loop in try { ... } catch (OutOfMemoryError e) { }. List two reasons this is a bad fix and describe a better approach for a program that reads a very large file line by line.
Show solutions
  1. StackOverflowError.
  2. The heap.
  3. False. It extends VirtualMachineError, which extends Error. (I checked this by running new OutOfMemoryError() instanceof VirtualMachineError, which printed true, and by the Javadoc pages for these classes.)
  4. Add a base case: static int sum(int n) { if (n <= 0) return 0; return n + sum(n - 1); }. The base case returns without a call, and each call moves n toward it. A loop is another valid fix.
  5. For example: static or long-lived collections that only grow (caches, histories, registries); listeners or sessions that are never removed. (Heap analysis tools are a later topic.)
  6. (a) StackOverflowError. (b) OutOfMemoryError (a leak: all messages stay reachable). (c) OutOfMemoryError (a genuinely large job).
  7. Reasons: the error is swallowed, so nothing is logged and the real problem remains (a catch block that does nothing was a mistake in BEG-029); after the error, the program may be unable to allocate anything, so it continues in an unreliable state. Better: process the file line by line (BEG-034 taught buffered readers) and keep only what you need, so memory use does not grow with file size.

Mini Challenge

Write a recursive method countDown(int n) that prints n down to 1, first with a deliberate bug (no base case) and then fixed. Run both, record which line of the stack trace repeats in the failing version, and in one sentence say which stack frames would exist at the moment of the overflow.

Summary & Next Lesson

Key takeaways

  • StackOverflowError: the stack is full, usually from recursion without a reachable base case.
  • OutOfMemoryError: the heap is full of reachable objects, after the collector has done what it can.
  • Both are Errors, so you fix the cause rather than catch and continue.
  • A stack trace that repeats shows the loop of calls. A steadily rising heap shows a leak.
  • Memory size options and leak-hunting tools come in later levels.

Next lesson

Next is Demo: drawing stack and heap, then checking in the debugger, where you draw a short program on paper and check the drawing against the debugger's variables panel.

Self-check

  1. What usually causes a StackOverflowError?
  2. Why is OutOfMemoryError thrown only after the garbage collector has tried?
  3. Is it good practice to catch these errors and keep running?
  4. What is the first question to ask about an OutOfMemoryError?
  5. Does a bigger heap fix a StackOverflowError?
Show answers
  1. Recursion (or mutual calls) that never reaches its stopping condition, so frames keep piling up.
  2. Because the JVM frees all unreachable objects first; the error means that even then there is not enough space for the request.
  3. No. Fix the cause. A top-level handler that logs and shuts down is the rare exception.
  4. What is keeping so many objects reachable (or is the job simply too large)?
  5. No. The stack and the heap are separate areas; the stack depends on call depth.

References

  • StackOverflowError (Java SE 21 API): verified that it is thrown when an application recurses too deeply and that it extends VirtualMachineError.
  • OutOfMemoryError (Java SE 21 API): verified that it is thrown when the JVM cannot allocate an object because it is out of memory and the garbage collector cannot make more available, and that it extends VirtualMachineError.
  • Not verified from a fetched page: the statement that Error is for problems applications should not normally catch (the fetched pages only implied it), and the -Xmx option name (I used it successfully in my own run on JDK 21, but did not read the java command documentation).
  • Code was compiled and run on JDK 21 and the output copied.

Practice

Apply this lesson with the module's lab, exercises and quiz.

Lab and exercises Quiz

Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.

Continue to next lesson

Type to search levels, phases, modules and lessons.