Skip to content
Ameen Abozaid
PublishedBEG-029-L02

The exception hierarchy

  • Lesson 2 of 8
  • 12 min read
  • Beginner
On This Lesson 11 topics

Lesson Overview

In the last lesson you met several exceptions with different names and different causes. They are not a random pile. Java arranges them in a family tree, and where an exception sits in the tree decides how the compiler treats it and how you should respond.

Staying with our fire-alarm picture: a building has different alarms for different dangers. Some mean "a person lit a toaster; handle it locally" and some mean "the building is on fire; get out". And some alarms are so serious that the safety law says every room must have a written plan for them. The exception tree is the building's alarm chart.

This lesson teaches you to place any exception in one of three groups: checked exceptions, unchecked exceptions (runtime exceptions), and errors. You will see how the compiler treats each, and a rule of thumb for choosing one when you design your own.

Why this matters: the group decides whether the compiler forces you to deal with a problem. Misreading it is behind both "my code will not compile" confusion and the habit of catching things that should never be caught.

This lesson builds on inheritance (BEG-022), where you saw extends, superclasses, and the way every class descends from Object, and on the field guide from "What goes wrong at runtime".

Learning Objectives

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

  • Draw the top of the tree: Throwable, Error, Exception, RuntimeException.
  • Classify any exception as checked, unchecked, or an error by following its superclasses.
  • Explain what the compiler requires for each group.
  • Say why code should not try to recover from an Error.
  • Choose between checked and unchecked for a new exception using the rule of thumb.
  • Recognize the compile error produced by an unhandled checked exception.

Written Explanation

The tree

Every exception and error in Java is a class, and all of them descend from one class: java.lang.Throwable. The Java API documentation says only Throwable and its subclasses can be thrown with throw or named in a catch. Below it the tree splits:

text
Object
  Throwable
    Error                      (serious problems with the JVM or system)
      OutOfMemoryError, StackOverflowError, ...
    Exception                  (problems your program may meet)
      IOException              (checked)
        FileNotFoundException
      ... other checked exceptions
      RuntimeException         (unchecked)
        IllegalArgumentException
          NumberFormatException
        IllegalStateException
        NullPointerException
        ArithmeticException
        ArrayIndexOutOfBoundsException (via IndexOutOfBoundsException)

You can check any exception's ancestry with a short program that follows getSuperclass() upward. Real output for three types:

java
public class Chain {
    static void show(Throwable t) {
        StringBuilder sb = new StringBuilder(t.getClass().getSimpleName());
        Class<?> c = t.getClass().getSuperclass();
        while (c != null) {
            sb.append(" -> ").append(c.getSimpleName());
            c = c.getSuperclass();
        }
        System.out.println(sb);
    }

    public static void main(String[] args) {
        show(new NumberFormatException());
        show(new java.io.FileNotFoundException());
        show(new StackOverflowError());
    }
}
text
NumberFormatException -> IllegalArgumentException -> RuntimeException -> Exception -> Throwable -> Object
FileNotFoundException -> IOException -> Exception -> Throwable -> Object
StackOverflowError -> VirtualMachineError -> Error -> Throwable -> Object

(That program uses Class and getSuperclass() only to show the tree; you will not need them in everyday code.)

The three groups

The group of a class depends on one thing: which branch it descends from.

GroupDescends fromThe compiler...Typical cause
ErrorErrordoes not force you to handle itThe JVM or system is in trouble
Unchecked exceptionRuntimeExceptiondoes not force you to handle itA bug in the program: wrong argument, null, bad index
Checked exceptionException, but not RuntimeExceptionforces you to handle or declare itA condition outside your control that the caller can often recover from

The Java API documentation states this rule: Throwable and any subclass that is not also a subclass of RuntimeException or Error is a checked exception for compile-time purposes. Unchecked is the name for the other two groups together.

So a short test works for any class: walk up. If you meet Error, it is an error. If you meet RuntimeException, it is unchecked. If you reach Exception without meeting either, it is checked.

Errors: leave them alone

An Error signals a serious problem in the runtime or environment, such as OutOfMemoryError (the JVM could not get memory) or StackOverflowError (the call stack ran out, as in runaway recursion from BEG-014). The Oracle tutorial describes errors as external conditions an application usually cannot anticipate or recover from. By the time one is thrown, your program has probably lost the resources it needs to repair itself.

The practical rule: do not write code that catches Error. Fix the underlying cause instead, for example by adding the missing base case to a recursive method. A running server may end up logging the error and restarting; that is a decision for the platform, not for ordinary business code.

Unchecked exceptions: bugs

RuntimeException and its children usually mean the program has a bug: the caller passed null, an index is wrong, a value is out of the allowed range. The compiler does not make you declare or catch them, and the Oracle tutorial says it is usually better to fix the bug than to catch the exception.

You already met several. IllegalArgumentException and IllegalStateException, which you threw in the encapsulation module, are unchecked: the compiler never asked you to wrap withdraw in anything.

Checked exceptions: the compiler asks for a plan

A checked exception is a problem a well-written program should anticipate: a file may not exist, a network may be down. The compiler enforces what the Oracle tutorial calls the Catch or Specify Requirement: any code that can throw a checked exception must either catch it, or declare that the method may throw it. If you do neither, the code does not compile.

Here is the error. The program tries to throw an IOException and does nothing about it:

java
public class Bad {
    public static void main(String[] args) {
        System.out.println("Start");
        throw new java.io.IOException("disk full");
    }
}

Real compiler output:

text
Bad.java:4: error: unreported exception IOException; must be caught or declared to be thrown
        throw new java.io.IOException("disk full");
        ^
1 error

The message tells you exactly what the language wants: caught (next lesson) or declared (the lesson after). Change the type to IllegalStateException and the same program compiles, runs, and prints Start followed by a stack trace, because no plan is required for unchecked exceptions.

Rule of thumb: which kind to choose

When you design your own exceptions later in this module, you will pick a branch. The traditional advice is:

  • Checked when the caller can reasonably recover and the failure is part of the method's honest contract: "this file might not be there", "this account might not have enough money". The compiler makes sure nobody forgets the case.
  • Unchecked for programming mistakes, such as passing null or a negative quantity. The caller should have checked first, and handling it everywhere would only add noise.

This is a guideline, not a law. The Oracle tutorial notes that some programmers use unchecked exceptions to avoid the requirement, but generally advises against that. Many modern frameworks also prefer unchecked exceptions in their APIs. Keep the guideline in mind and look at what each library or team does.

Examples

Example 1: sort five exceptions by walking up (simple)

Take ArithmeticException. Its parent is RuntimeException, so it is unchecked. Take OutOfMemoryError. Its ancestors include Error, so it is an error. Take FileNotFoundException. Parent: IOException, then Exception. It never meets RuntimeException or Error, so it is checked. Take NumberFormatException: parent IllegalArgumentException, then RuntimeException, so unchecked. Take InterruptedException: a direct child of Exception, so checked.

Example 2: let the program classify them (practical)

You can encode the "walk up" rule using instanceof, which you learned in the polymorphism module:

java
public class Kind {
    static String kind(Throwable t) {
        if (t instanceof Error) return "error";
        if (t instanceof RuntimeException) return "unchecked";
        return "checked";
    }

    public static void main(String[] args) {
        Throwable[] samples = {
            new java.io.FileNotFoundException(),
            new ArithmeticException(),
            new StackOverflowError(),
            new NumberFormatException(),
            new InterruptedException(),
            new IllegalStateException()
        };
        for (Throwable t : samples) {
            System.out.println(t.getClass().getSimpleName() + ": " + kind(t));
        }
    }
}

Real output:

text
FileNotFoundException: checked
ArithmeticException: unchecked
StackOverflowError: error
NumberFormatException: unchecked
InterruptedException: checked
IllegalStateException: unchecked

This is only a teaching helper: the compiler, not your code, applies the rule in real programs. The order matters, because a type that is neither an Error nor a RuntimeException falls through to "checked".

Example 3: designing the bank's two failures (realistic)

The bank has two kinds of failure.

  1. A teller's code calls deposit(-50). Nobody can legitimately deposit a negative amount, so the caller made a mistake. This is a bug: IllegalArgumentException, unchecked.
  2. A customer asks for 500 from an account holding 70. Nothing is wrong with the code: it is a normal business situation, the caller can show a message and carry on. This is a good candidate for a checked exception, so that every caller must decide what to do. Until now we used IllegalStateException for this. In lesson 5 we will replace it with a dedicated checked exception.

Seeing the difference is the skill: one failure means "your code is wrong" and the other means "the request cannot be met right now".

Backend Insight

Frameworks you will use later lean on this tree. A web service typically treats a failure in the request (bad input, a rule violated) differently from a failure of the system (database unreachable). Both can be exceptions, but the first yields a "client error" response and the second a "server error" response. Knowing the family of an exception tells you where to look: a NumberFormatException points at the caller's data, while an OutOfMemoryError points at the server's resources.

Transaction handling follows the same split. In Spring, which you will meet later in the roadmap, a transaction by default rolls back on unchecked exceptions and errors but not on checked ones. We mention this only to show that the three groups matter in real jobs; treat the detail as something to confirm in that framework's documentation when you reach it.

Common Mistakes

Mistake 1: Assuming "checked" means "serious"

  • Incorrect: "A NullPointerException must be worse than an IOException because it crashes the program."
  • Correct: "Checked and unchecked say who is forced to plan, not how bad it is." A checked exception is often routine.

Mistake 2: Catching Error or Throwable to be safe

Code that catches Throwable also catches every Error, including ones the program cannot recover from. Catch the narrowest type you can handle meaningfully, a theme of the next lesson.

Mistake 3: Using unchecked exceptions to silence the compiler

Switching an IOException to a RuntimeException only so the compiler stops complaining does not remove the problem. It removes the reminder. Choose the type by who can recover, not by what makes the build pass.

Mistake 4: Judging the group by the name

InterruptedException is checked and IllegalStateException is unchecked, and neither name says so. Walk up the tree, or look at the API documentation, to be sure.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. Which class is the root of everything that can be thrown?

Show solution A1

A1. java.lang.Throwable.

A2. Name the three groups and say which ones the compiler forces you to handle.

Show solution A2

A2. Checked exceptions, unchecked exceptions (descendants of RuntimeException), and errors. The compiler forces you to handle or declare only the checked ones.

A3. Classify each as checked, unchecked, or error: IllegalStateException, FileNotFoundException, StackOverflowError.

Show solution A3

A3. IllegalStateException: unchecked. FileNotFoundException: checked. StackOverflowError: error.

Level B: Application

B1. This program does not compile. State why in one sentence, and give two different ways to make it compile (describe them; you do not need the syntax for one of them yet).

java
public class Task {
    public static void main(String[] args) {
        throw new Exception("not ready");
    }
}
Show solution B1

B1. It throws Exception, which is checked, but main neither catches nor declares it. Way 1: wrap the throw in a try/catch (next lesson). Way 2: declare that main may throw it, with a throws Exception clause (lesson 4). Changing it to an unchecked type such as IllegalStateException also compiles, but only if the problem really is a bug.

B2. A teammate writes catch (Throwable t) around a call to a recursive method "just in case". Explain two problems with this.

Show solution B2

B2. It also catches StackOverflowError or OutOfMemoryError, which the program usually cannot repair. And it hides the real cause: with no fix to the missing base case the code may fail again or leave the program in a damaged state.

B3. Using the walk-up test, classify NegativeArraySizeException, given only that its parent is RuntimeException, and TimeoutException, given only that its parent is Exception.

Show solution B3

B3. NegativeArraySizeException descends from RuntimeException, so it is unchecked. TimeoutException descends from Exception without passing through RuntimeException or Error, so it is checked.

Level C: Challenge

C1. For each situation, choose checked or unchecked and justify it in one sentence: (a) a method receives a null customer; (b) a method tries to read a data file that the user may have moved; (c) a method is asked to set a price of -1.

Show solution C1

C1. (a) Unchecked: passing null is the caller's mistake. (b) Checked: a missing file is a normal condition and the caller can ask for another path. (c) Unchecked: a negative price is an invalid argument, so a bug in the caller.

C2. Write a short program like Kind that prints the kind of ClassCastException, IOException, and AssertionError. Predict the output before running it, then verify.

Show solution C2

C2. Prediction: ClassCastException unchecked (a RuntimeException), IOException checked, AssertionError error (it descends from Error). Add the three instances to the samples array and run to verify.

Mini Challenge

Draw your own exception chart on paper or in a text file, with Throwable at the top and three columns below it: errors, checked, unchecked. Place at least eight exceptions in it, including six from this module. For each, add a five-word note on a typical cause. Then use the API documentation (searching for the class name will find it) to confirm two classes you were unsure about.

Check your work: every exception sits under the right branch, and none of your notes say that "checked means worse".

Summary & Next Lesson

Key takeaways

  • Throwable is the root. It splits into Error and Exception; RuntimeException is a branch under Exception.
  • Errors: serious system problems. Do not catch them.
  • Unchecked (RuntimeException and children): usually bugs. The compiler does not force a plan.
  • Checked (other Exceptions): anticipated problems the caller may recover from. The compiler forces a catch or a declaration.
  • Rule of thumb: checked where the caller can recover, unchecked for programming mistakes. It is a guideline that teams apply in different ways.

Next lesson

You can now tell which exceptions the compiler will ask you to plan for. The next lesson, "try, catch, finally", teaches the plan itself: how to catch an exception and keep the program running.

Self-check

  1. Which two classes sit directly under Throwable in this lesson?
  2. How can you tell whether a class is a checked exception?
  3. Is NumberFormatException checked or unchecked, and why?
  4. What does the compiler say if you throw a checked exception without a plan?
  5. Why should ordinary code not catch OutOfMemoryError?
Show answers
  1. Error and Exception.
  2. Walk up its superclasses: if it reaches Exception without passing through RuntimeException or Error, it is checked.
  3. Unchecked, because it descends from IllegalArgumentException, which descends from RuntimeException.
  4. unreported exception ...; must be caught or declared to be thrown.
  5. It signals a system-level failure the program usually cannot repair, so catching it only hides the problem.

References

  • Oracle, The Java Tutorials, The Catch or Specify Requirement. Fetched. Supports the three kinds (checked, error, runtime), which of them the compiler enforces, and the advice to fix runtime-exception bugs and avoid using unchecked exceptions only to skip the requirement.
  • Oracle, Java SE 21 API, Throwable. Fetched. Supports that only throwables can be thrown or caught, and the rule that any throwable not under RuntimeException or Error is checked.
  • The ancestry chains, the classification output, and the compiler message above were produced with JDK 21.0.10.
  • Not verified: the statement about default rollback behavior in Spring is from general knowledge and is hedged in the text; confirm it in the Spring documentation when you reach that module. The parent classes given in exercise B3 are supplied as assumptions in the question. The C2 answer was run.

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.