Fix java.lang.StackOverflowError in Java

beginnerโ˜• Java2026-03-17| Java 8+, JVM (Windows / Linux / macOS), any Java application using recursion or deep call chains

Error Message

java.lang.StackOverflowError
#java#recursion#stack

The Error

You run your Java application and the JVM throws:

Exception in thread "main" java.lang.StackOverflowError
	at com.example.MyClass.calculate(MyClass.java:12)
	at com.example.MyClass.calculate(MyClass.java:12)
	at com.example.MyClass.calculate(MyClass.java:12)
	... (repeated hundreds of times)

Same line, repeated hundreds of times. That's the tell โ€” the JVM exhausted its call stack space before your code could finish.

Why This Happens

Each method call in Java gets its own stack frame. The JVM reserves a fixed chunk of memory for this per thread โ€” typically 512KB to 1MB. If calls nest too deeply without unwinding, that space fills up fast.

Three scenarios cause this most often:

  • Missing or wrong base case in a recursive method
  • Mutual recursion โ€” method A calls method B, which calls method A
  • Accidental self-calls โ€” a getter calling itself, or a toString() that triggers another toString()

Fix 1 โ€” Correct the Base Case

Find the repeated line in your stack trace, open that method, and check whether it has a stopping condition.

Broken:

public int factorial(int n) {
    return n * factorial(n - 1);  // No base case โ€” runs forever
}

Fixed:

public int factorial(int n) {
    if (n  stack = new ArrayDeque<>();
    stack.push(root);
    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        System.out.println(node.val);
        if (node.right != null) stack.push(node.right);
        if (node.left != null) stack.push(node.left);
    }
}

Fix 3 โ€” Hunt Down Accidental Self-Calls

This one catches people off guard. A toString() that references itself instead of a field will spin forever:

// Bug: toString calls itself
@Override
public String toString() {
    return "User: " + toString();  // Should be this.name, not the method
}

// Fix:
@Override
public String toString() {
    return "User: " + this.name;
}

Also watch for Lombok or IDE-generated equals()/hashCode() on entities with circular references โ€” this is common in bidirectional JPA relationships. Annotate the back-reference field with @ToString.Exclude or @EqualsAndHashCode.Exclude to break the cycle.

Fix 4 โ€” Bump the Stack Size (Last Resort)

Can't refactor right now? You can buy time with the -Xss flag. The default is usually 512k or 1m โ€” try quadrupling it:

java -Xss4m -jar your-app.jar

Need a larger stack for one specific thread? Set it in the Thread constructor:

Thread thread = new Thread(null, () -> {
    runDeepRecursion();
}, "deep-thread", 4 * 1024 * 1024);  // 4MB stack
thread.start();

This doesn't fix anything. With large enough input, the error will come back. Use this while you work on the real fix โ€” not instead of it.

Verification Steps

Once you've applied a fix, run through these checks:

  • Re-run the exact input that crashed the app. The error should be gone.
  • Test edge cases: n = 0, n = 1, negative values, empty lists.
  • Throw a large input at your iterative version โ€” try n = 100000 โ€” to confirm it holds up.
  • Lock it in with a unit test so it can't regress:
@Test
public void testFactorialLargeInput() {
    // Should not throw StackOverflowError
    assertDoesNotThrow(() -> factorial(10000));
}

Prevention Tips

  • Write the base case first, at the top of the method, before any recursive call.
  • For user-controlled input, cap recursion depth explicitly:
public void traverse(Node node, int depth) {
    if (node == null || depth > 1000) return;
    traverse(node.next, depth + 1);
}
  • Graphs and trees can have cycles. Track visited nodes in a Set to avoid looping forever.
  • In code review, treat every recursive method as a risk โ€” ask whether the base case is correct and whether it can actually be reached.

Related Error Notes