Definition
- Error extends
Throwabledirectly and represents serious JVM-level failures:OutOfMemoryError(heap exhausted),StackOverflowError(infinite recursion),VirtualMachineError(JVM corrupted). Errors are always unchecked and should not be caught because the JVM is in an unreliable state. - Exception extends
Throwabledirectly and represents application-level problems:IOException(file not found),NullPointerException(programming bug),SQLException(database down). Exceptions are designed to be caught and handled. - Both share the same parent (
Throwable) but Error signals "the system is broken" while Exception signals "something went wrong in the application."
An Error is like the bank building collapsing during an earthquake. You cannot handle a collapsed building by filling out a form. Everyone evacuates and the building is rebuilt. An Exception is like a cheque bouncing due to insufficient funds. The teller handles it normally: notify Priya, reverse the transaction, log the event. The bank building is fine. The system is designed to handle bounced cheques.
Understanding the Concepts
What is an Error in Java?
An Error is a subclass of Throwable that indicates a serious problem the application should not try to recover from. When an Error occurs, the JVM is typically in an unreliable state, and any attempt to continue may produce corrupted results.
package com.bank.demo;
import java.util.ArrayList;
import java.util.List;
public class ErrorDemo {
// OutOfMemoryError: heap space exhausted
public static void causeOOM() {
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]); // allocate 1MB each iteration
}
// Eventually: java.lang.OutOfMemoryError: Java heap space
}
// StackOverflowError: call stack exhausted by recursion
public static int infiniteRecursion(int n) {
return infiniteRecursion(n + 1); // no base case
// Eventually: java.lang.StackOverflowError
}
public static void main(String[] args) {
try {
infiniteRecursion(0);
} catch (StackOverflowError e) {
System.out.println("StackOverflowError caught (for demo only)");
System.out.println("In production, do NOT catch Errors");
}
}
}
StackOverflowError caught (for demo only)
In production, do NOT catch Errors
What is an Exception in Java?
An Exception is a subclass of Throwable that represents conditions an application might want to catch and handle. Exceptions divide into checked (extends Exception but not RuntimeException) and unchecked (extends RuntimeException).
package com.bank.service;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
public class TradeService {
// IOException is a checked Exception: recoverable
public String loadTrades(String path) {
try {
return Files.readString(Path.of(path));
} catch (IOException e) {
System.out.println("File not found, using fallback data");
return "FALLBACK_DATA";
}
}
// NullPointerException is an unchecked Exception: programming bug
public BigDecimal getBalance(String accountId) {
if (accountId == null) {
throw new IllegalArgumentException("accountId must not be null");
}
return new BigDecimal("75000.00");
}
public static void main(String[] args) {
TradeService service = new TradeService();
String data = service.loadTrades("/nonexistent/trades.csv");
System.out.println("Data: " + data);
BigDecimal balance = service.getBalance("SAV2001");
System.out.println("Balance: Rs." + balance);
}
}
File not found, using fallback data
Data: FALLBACK_DATA
Balance: Rs.75000.00
How is Error different from Exception?
| Feature | Error | Exception |
|---|---|---|
| Extends | Throwable directly | Throwable directly |
| Represents | JVM-level failure | Application-level problem |
| Checked/Unchecked | Always unchecked | Checked or unchecked |
| Should you catch? | No (JVM is unreliable) | Yes (handle and recover) |
| Examples | OutOfMemoryError, StackOverflowError | IOException, NullPointerException |
| Recovery possible? | Generally no | Generally yes |
| Cause | JVM resource exhaustion, native failure | Bad input, missing file, network failure |
package com.bank.demo;
public class ErrorVsExceptionDemo {
public static void main(String[] args) {
// Error: extends Throwable, not Exception
System.out.println("Error superclass: " + Error.class.getSuperclass().getSimpleName());
System.out.println("Exception superclass: " + Exception.class.getSuperclass().getSimpleName());
System.out.println("RuntimeException superclass: " + RuntimeException.class.getSuperclass().getSimpleName());
// Error is NOT an Exception
System.out.println("Error is-a Exception: " + Exception.class.isAssignableFrom(Error.class));
// Exception IS-A Throwable
System.out.println("Exception is-a Throwable: " + Throwable.class.isAssignableFrom(Exception.class));
}
}
Error superclass: Throwable
Exception superclass: Throwable
RuntimeException superclass: Exception
Error is-a Exception: false
Exception is-a Throwable: true
Why should you not catch Error?
When an Error occurs, the JVM is in an unreliable state. Catching it and continuing means your application runs on a broken foundation. Any "recovery" code might fail too, producing corrupted data silently.
package com.bank.demo;
public class DontCatchErrorDemo {
// WRONG: catching Error hides JVM failure
public static void wrongApproach() {
try {
byte[] huge = new byte[Integer.MAX_VALUE];
} catch (OutOfMemoryError e) {
System.out.println("Caught OOM, continuing...");
// This log statement might ALSO fail (no memory to create the String)
// The JVM is in an unpredictable state. GC might be thrashing.
// Any object creation might throw another OOM.
}
}
// WRONG: catching Throwable catches both Error and Exception
public static void wrongBroadCatch() {
try {
processLargeBatch();
} catch (Throwable t) {
System.out.println("Caught: " + t.getClass().getSimpleName());
// Accidentally catches OutOfMemoryError, StackOverflowError.
// Hides JVM failures as if they were application exceptions.
}
}
// CORRECT: catch specific Exception types
public static void correctApproach() {
try {
processLargeBatch();
} catch (IllegalArgumentException e) {
System.out.println("Bad input: " + e.getMessage());
} catch (RuntimeException e) {
System.out.println("Unexpected error: " + e.getMessage());
}
// Errors propagate naturally and crash the JVM cleanly.
}
private static void processLargeBatch() {
System.out.println("Processing batch...");
}
public static void main(String[] args) {
correctApproach();
}
}
Processing batch...
When is catching Error acceptable?
The only acceptable case is in a top-level uncaught exception handler for logging before the JVM shuts down. The goal is to capture evidence of the failure, not to recover.
package com.bank.demo;
public class UncaughtHandlerDemo {
public static void main(String[] args) {
// Top-level handler for logging before shutdown
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
System.err.println("CRITICAL: Unhandled " + throwable.getClass().getSimpleName()
+ " in thread " + thread.getName());
System.err.println("Message: " + throwable.getMessage());
// In production: write to file, send alert, then let JVM die
});
System.out.println("Uncaught handler registered");
System.out.println("If OOM or SOE occurs, it will be logged before JVM exit");
}
}
Uncaught handler registered
If OOM or SOE occurs, it will be logged before JVM exit
What are all the common Error types?
Java defines several Error subclasses. Each represents a different category of JVM-level failure.
OutOfMemoryError: heap space exhausted, Metaspace full, or GC overhead limit exceeded.StackOverflowError: per-thread call stack exhausted, usually from infinite recursion.NoClassDefFoundError: a class was present at compile time but missing at runtime.ExceptionInInitializerError: a static initializer threw an exception.VirtualMachineError: the JVM is broken or out of resources.LinkageError: class definition has changed incompatibly since compilation.
package com.bank.demo;
public class ErrorTypesDemo {
public static void main(String[] args) {
System.out.println("Common Error types:");
System.out.println(" OutOfMemoryError: heap exhausted");
System.out.println(" StackOverflowError: recursion too deep");
System.out.println(" NoClassDefFoundError: class missing at runtime");
System.out.println(" ExceptionInInitializerError: static init failed");
// All are unchecked
System.out.println("\nAll extend Error (not Exception):");
System.out.println(" OOM extends Error: " +
Error.class.isAssignableFrom(OutOfMemoryError.class));
System.out.println(" SOE extends Error: " +
Error.class.isAssignableFrom(StackOverflowError.class));
}
}
Common Error types:
OutOfMemoryError: heap exhausted
StackOverflowError: recursion too deep
NoClassDefFoundError: class missing at runtime
ExceptionInInitializerError: static init failed
All extend Error (not Exception):
OOM extends Error: true
SOE extends Error: true
What are common mistakes when dealing with Error vs Exception?
package com.bank.demo;
public class ErrorMistakesDemo {
// WRONG: catch(Throwable) in application code
public static void wrongCatchThrowable() {
try {
riskyOperation();
} catch (Throwable t) {
// Catches Error + Exception.
// OOM, SOE, and other Errors are silently swallowed.
System.out.println("Wrong: caught " + t.getClass().getSimpleName());
}
}
// CORRECT: catch(Exception) to avoid catching Errors
public static void correctCatchException() {
try {
riskyOperation();
} catch (Exception e) {
// Only catches Exception and subclasses.
// Errors propagate naturally.
System.out.println("Correct: caught " + e.getClass().getSimpleName());
}
}
private static void riskyOperation() {
throw new RuntimeException("Application error");
}
public static void main(String[] args) {
wrongCatchThrowable();
correctCatchException();
}
}
Wrong: caught RuntimeException
Correct: caught RuntimeException
What should a senior engineer know?
- OOM does not always mean heap is full. There are multiple OOM variants:
Java heap space,Metaspace,GC overhead limit exceeded,unable to create new native thread. Each has a different root cause and fix. - StackOverflowError is per-thread. One thread can SOE while others continue. The JVM does not necessarily die. But the failing thread is gone.
- NoClassDefFoundError vs ClassNotFoundException. NCDFE is an Error (class existed at compile time, missing at runtime). CNFE is a checked Exception (dynamic loading failed). They are often confused in interviews.
- Heap dump on OOM. Add
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/to JVM flags. This generates a heap dump automatically when OOM occurs, which you can analyze with Eclipse MAT or VisualVM.
What are common interview follow-up questions?
- What is the parent of both Error and Exception? (Throwable.)
- Can you catch Error? (Yes, but you should not in application code.)
- Is Error checked or unchecked? (Unchecked, always.)
- What is the difference between catching Exception and catching Throwable? (Throwable also catches Error.)
- Name three Error subclasses. (OutOfMemoryError, StackOverflowError, NoClassDefFoundError.)
Visual Overview
Throwable
┌────┴────┐
│ │
Error Exception
(DON'T (DO catch)
catch)
│ │
┌─────┬──┘ ┌────┴──────────┐
│ │ │ │
OOM SOE IOException RuntimeException
SQLException (unchecked)
│
┌──────┴──────┐
│ │
NPE IAE
┌─────────────────────────────────────────────┐
│ Error = JVM broken, do not catch │
│ Exception = app problem, catch and handle │
│ catch(Exception) is safe │
│ catch(Throwable) catches Errors too │
└─────────────────────────────────────────────┘
Key Takeaway
Error and Exception both extend Throwable, but they represent fundamentally different categories of problems. Error represents JVM-level failures like OutOfMemoryError (heap exhausted), StackOverflowError (infinite recursion), and NoClassDefFoundError (class missing at runtime). You should not catch Errors because the JVM is in an unreliable state. Exception represents application-level problems like IOException (file not found), NullPointerException (programming bug), and SQLException (database issue). Exceptions are designed to be caught and handled with recovery logic.
In the batch processing service at National Bank of India, Rajesh once caught Throwable instead of Exception during overnight settlement. An OutOfMemoryError occurred during a large batch, but the catch block swallowed it and continued processing. The result was silently corrupted settlement records. After switching to catch (Exception e), the OOM propagated naturally, the JVM crashed cleanly, the ops team received an immediate alert, and no data was corrupted.
The practical rule is simple: use catch (Exception e) or more specific types in your application code. Never catch (Throwable t). Let Errors propagate and crash the JVM cleanly. The only place to intercept Errors is a top-level uncaught exception handler that logs evidence before the process exits.
Remember one sentence: Errors mean the JVM is broken and you should let it die cleanly, while Exceptions mean the application had a problem and you should handle it gracefully.
---