Errors
In short: Serious problems that a Java program normally shouldn’t try to catch itself — e.g. OutOfMemoryError or StackOverflowError, both subclasses of java.lang.Error.
In more detail: Java strictly distinguishes between Error (a problem at the JVM level, usually not meaningfully handleable) and exceptions (a problem at the program level that can be specifically caught and handled). Both inherit from the shared superclass Throwable.
In Depth
Throwable
├── Error (JVM level, usually not meaningfully catchable)
│ ├── OutOfMemoryError heap memory exhausted
│ └── StackOverflowError too-deep/infinite recursion
└── Exception (program level, specifically handleable)
├── IOException (checked - compiler enforces handling)
└── RuntimeException (unchecked)
├── NullPointerException
├── ArrayIndexOutOfBoundsException
└── ArithmeticException
A StackOverflowError typically results from recursion with no (or a faulty) base case — every method call creates a new stack frame, until the reserved memory for the call stack is full. An OutOfMemoryError occurs when the heap (where objects live) is completely exhausted, often due to memory leaks (objects that remain referenced even though they’re no longer needed, and are therefore never collected by the garbage collector). Technically it IS possible to write catch (Error e), but in the vast majority of cases it’s pointless: once the heap is full or the stack has overflowed, the JVM is often already in such an unstable state that even the catch block itself can no longer run reliably — hence the convention to fundamentally NOT catch Error, and instead fix the underlying cause (infinite recursion, memory leak).
Typical causes of StackOverflowError
Besides faulty recursion with no base case, a StackOverflowError can also occur with CORRECT but too-deep recursion — e.g. when recursively processing a very long list (several hundred thousand elements), where every level of recursion consumes its own stack frame. Here, either an iterative implementation with an explicit loop instead of recursion helps, or — if available — tail-call optimisation, which Java (unlike, for example, Scala) does NOT guarantee to support, even if the recursive call is technically the last statement.
// Dangerous for very long lists: recursive summation
int sumRecursive(List<Integer> list, int index) {
if (index >= list.size()) return 0;
return list.get(index) + sumRecursive(list, index + 1); // one stack frame per element
}
// More robust: iterative variant with no recursion-depth limit
int sumIterative(List<Integer> list) {
int sum = 0;
for (int value : list) sum += value;
return sum;
}Specifically diagnosing OutOfMemoryError
For an OutOfMemoryError in production, the bare error alone rarely helps much — the JVM can be configured with the flag -XX:+HeapDumpOnOutOfMemoryError to automatically write a heap dump when it occurs, which can then be analysed with tools like Eclipse MAT, to see exactly which objects are occupying memory and why they couldn’t be collected by the garbage collector.
See also: Exceptions, Debugging, Error Codes