Back to Exception Handling Fundamentals
Q4Question 4 of 4

What is the difference between Error and Exception?

Key Concepts
  • Error represents serious JVM-level problems like OutOfMemoryError and StackOverflowError that applications should not catch
  • Exception represents application-level problems like IOException and NullPointerException that can be caught and handled
  • Both Error and Exception extend Throwable, but they serve fundamentally different purposes
  • Errors are always unchecked and the JVM is in an unreliable state when they occur
  • Catching Throwable or Error in application code hides JVM failures and produces corrupted results
  • The only acceptable Error catching is in a top-level uncaught exception handler for logging before shutdown

Definition

Error represents unrecoverable JVM-level problems like OutOfMemoryError that you should not catch, while Exception represents application-level problems like IOException that you can catch and handle gracefully.
  • Error extends Throwable directly 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 Throwable directly 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.

JAVA
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");
        }
    }
}
TEXT
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).

JAVA
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);
    }
}
TEXT
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 |

JAVA
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));
    }
}
TEXT
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.

JAVA
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();
    }
}
TEXT
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.

JAVA
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");
    }
}
TEXT
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.
JAVA
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));
    }
}
TEXT
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?

JAVA
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();
    }
}
TEXT
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

DIAGRAM
                     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.

---

Share
Use ← → arrow keys to navigate