CSCD210

The throwable hierarchy

Skillcscd210-the-throwable-hierarchyTextbookBJP Ch 6

Throwable, Error, Exception, RuntimeException

Student can identify the four layers of Java's exception hierarchy (Throwable, Error, Exception, RuntimeException), classify a given exception class as checked or unchecked by walking its parent chain, and explain why Error is a sibling of Exception rather than a subclass.

Java's exception machinery is built on a four-layer class hierarchy rooted at java.lang.Throwable. Everything that can be thrown is a subclass of Throwable; everything that can be caught is a subclass of Throwable.

            Throwable
           /         \
        Error         Exception
       (do not      /         \
        catch)  RuntimeException  (other checked exceptions:
                (unchecked)        IOException, SQLException, ...)
                /     \
        NullPointerException     ArithmeticException
        ArrayIndexOutOfBoundsException     IllegalArgumentException ...

(Shortened to NPE and AIOOBE below: the names are long; the acronyms are commonplace in Java stack traces and code reviews.)

Each layer has a specific role:

1. Throwable: the root. (Technically a concrete class: new Throwable("x") compiles and runs. Throwing it directly is legal but almost never the right move; the convention is to throw a Exception or RuntimeException subclass.) Defines the getMessage, printStackTrace, getStackTrace interface every thrown thing supports. Catching Throwable is legal but almost always wrong (see pitfalls). 2. Error: JVM-internal conditions like OutOfMemoryError, StackOverflowError, NoClassDefFoundError. Programmer code does not throw these; the JVM does. Application code does not catch them; they signal that the JVM itself is in trouble, and recovery is rarely possible. 3. Exception: the parent of every application-level exception, both checked and unchecked. IOException, SQLException, ParseException, and the entire RuntimeException subtree all live here. 4. RuntimeException: the unchecked subset of Exception. The compiler does not require try/catch or throws declarations for these. Subclasses include NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException, IllegalArgumentException, ClassCastException: programmer-bug indicators.

The "checked vs unchecked" cut

The compiler-rule split between checked and unchecked maps onto the class hierarchy:

  • Checked: Exception and its subclasses except RuntimeException and its subclasses. Examples: IOException, FileNotFoundException, SQLException.
  • Unchecked: RuntimeException and its subclasses, plus all of Error and its subclasses.

The class hierarchy is the rule. To know whether a given exception is checked, find its parent chain. If it goes through RuntimeException (or is Error), it is unchecked. Otherwise it is checked.

Why Error is separate from Exception

The hierarchy puts Error next to Exception, not inside it, so that catch (Exception e) does not accidentally catch OutOfMemoryError. The design intent: programmer code that catches "anything that went wrong with what I asked the JVM to do" can write catch (Exception e) and trust the JVM's internal errors will not be swallowed. The two siblings live in different worlds.

Production code that defensively catches everything uses catch (Throwable t), but that pattern is rare and usually a code smell. Bloch's Effective Java Item 77 covers when (rarely) it is justified.

In other languages

  • C++: there is a std::exception base class, but no enforced "everything thrown extends it." Anything can be thrown, including int. Less rigorous.
  • Python: BaseException is the analog of Throwable; Exception extends it; KeyboardInterrupt and SystemExit are BaseException but not Exception (analogous to Java's Error / Exception split).
  • C#: System.Exception is the analog; no enforced checked/unchecked distinction (everything is unchecked).
  • JavaScript: no enforced class hierarchy; anything can be thrown: strings, numbers, objects. ES6 introduced Error as a useful base but does not enforce its use.

Checked vs unchecked exceptions

Student can classify a given exception class as checked or unchecked by checking whether RuntimeException (or Error) appears in its parent chain, predict whether a method that throws it requires a throws clause, and identify the Bloch design intent (checked for recoverable, unchecked for programming errors).

Java is one of the few mainstream languages whose compiler enforces a rule about exceptions: every method that calls something that can throw a checked exception must either catch it or declare it on the method header with throws. Unchecked exceptions have no such requirement. The compiler enforces the rule mechanically (JLS §11.2: the handle-or-declare rule).

//  Checked: compile error without `throws` or `try`:
public static void readFile(final String name) throws FileNotFoundException {
    new Scanner(new File(name));    //  FileNotFoundException is checked
}

//  Unchecked: compiler is silent either way:
public static int divide(final int a, final int b) {
    return a / b;                   //  ArithmeticException is unchecked
}

The two categories map cleanly onto the class hierarchy:

  • Checked: subclass of Exception but NOT of RuntimeException. Examples: IOException, FileNotFoundException, SQLException, ParseException.
  • Unchecked: subclass of RuntimeException (or Error). Examples: NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException, ArithmeticException.

To classify a new exception: walk the parent chain. If RuntimeException appears, unchecked. Otherwise checked.

The design intent

Bloch Effective Java Item 70 articulates Java's intent:

  • Use checked for recoverable conditions: situations where the caller might reasonably want to handle the failure and try something else (e.g., re-prompt for a filename, fall back to a default config, retry an HTTP request).
  • Use unchecked for programming errors: situations where the caller violated a precondition the method documented (null where non-null required, index out of range, divide by zero).

The compiler enforcement reflects the intent. For checked exceptions, the caller is forced to acknowledge that recovery may be needed; the type system makes the failure path visible. For unchecked, the failure is the programmer's bug to fix in code, not a runtime branch to handle.

In practice, Java's checked-exception design is controversial: Bloch himself notes that checked exceptions can be over-used and that languages like C# and Kotlin (which have no checked-exception concept) work fine. CSCD 210 teaches the rule because the JDK enforces it; CSCD 211 revisits whether to throw checked or unchecked in new code.

The CS1 student-facing summary

For CSCD 210 purposes:

  • FileNotFoundException is the canonical checked exception. Every method that opens a file must throws it (or catch).
  • NullPointerException is the canonical unchecked exception. No throws needed; bug shows at runtime.
  • Integer.parseInt throws NumberFormatException (unchecked) on bad input: no throws required, but careful programs catch it for user-facing input.

The five exceptions CSCD 210 students meet are catalogued in the roster that follows.

In other languages

  • C#: no checked-exception concept. Every exception is what Java calls "unchecked." The XML documentation comments are the only signal of which exceptions a method may throw.
  • Python: also no checked-exception concept. PEP 484 type hints can document exceptions, but the compiler does not enforce them.
  • Kotlin: explicitly removed checked exceptions from the language even when interoperating with Java; calling a Java method that declares throws IOException does not force a Kotlin caller to handle it.
  • Swift: has a different mechanism: throws is part of the function type, and callers must use try syntax. Conceptually similar to Java's checked exceptions but more flexible.

The five exceptions every CSCD 210 student meets

Student can name the five exception classes most likely to appear in CSCD 210 code (NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException, IllegalArgumentException, FileNotFoundException), classify each as checked or unchecked, and predict which of a method's behaviors will trigger each one.

By the end of CSCD 210, five exception classes will have appeared often enough that a student should recognize each by name, name what triggers it, and predict whether the compiler requires a throws clause.

| Class | Checked? | Triggered by | |-------|----------|--------------| | NullPointerException | unchecked | dereferencing null (x.method() when x is null) | | ArrayIndexOutOfBoundsException | unchecked | xs[i] when i is < 0 or >= xs.length | | ArithmeticException | unchecked | integer / 0 or % 0 (also BigDecimal rounding failures) | | IllegalArgumentException | unchecked | a method-author throws this manually when an argument is invalid | | FileNotFoundException | checked | new Scanner(new File(name)) when the file is missing |

Four are unchecked; one is checked. The checked one is the only of the five that the compiler will refuse to compile without a throws clause or try/catch.

Why these five

The list reflects the labs and exams CSCD 210 ships through. Every term:

  • NullPointerException fires when students forget that methods can return null (most common: forgetting to allocate an array; calling .length on the unallocated reference).
  • ArrayIndexOutOfBoundsException fires from off-by-one loop bounds, which array access and length covers.
  • ArithmeticException fires from / 0 in average-calculation code when the array is empty.
  • IllegalArgumentException is thrown by the student's own precondition checks: if (arr == null) throw new IllegalArgumentException(...). Throwing an IllegalArgumentException yourself covers the authoring side.
  • FileNotFoundException fires from new Scanner(new File(name)) when the file does not exist, and the rule that FileNotFoundException is checked is the detailed treatment.

Where each one is covered in depth

Each of the five is treated in full somewhere else in the course:

| Exception | Covered in depth by | |-----------|-------------| | NullPointerException | null as the no-object value, in classes and objects | | ArrayIndexOutOfBoundsException | array access and length | | ArithmeticException | no dedicated treatment, and the case that raises it is the average of an empty array | | IllegalArgumentException | throwing an IllegalArgumentException yourself | | FileNotFoundException | the rule that FileNotFoundException is checked |

This leaf is the roll-up: it names all five and points to where each is treated in detail. When a student says "I got an ArithmeticException," the tree's response is "look at the sum-and-average leaf."

Recognition by error message

The exception's toString output is the first thing students see in a stack trace. Recognizing the message format helps:

java.lang.NullPointerException: Cannot invoke "String.length()" because "name" is null
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
java.lang.ArithmeticException: / by zero
java.lang.IllegalArgumentException: array length must be non-negative
java.io.FileNotFoundException: data.txt (No such file or directory)

Java 14+ produces helpful NPE messages that name the variable. Pre-14 produces just NullPointerException with no detail. CSCD 210's grader droplet runs Java 17; expect the helpful form.

In other languages

  • C: none of these exists. Null-pointer dereferences segfault; array out-of-range is undefined behavior; division by zero is undefined behavior. Java's exceptions are the language-level safety net C lacks.
  • Python: the analogs are AttributeError (NPE), IndexError (AIOOBE), ZeroDivisionError (ArithmeticException), ValueError (IllegalArgumentException), FileNotFoundError (FNFE). All unchecked.
  • C#: every analog exists with a near-identical name. NullReferenceException, IndexOutOfRangeException, DivideByZeroException, ArgumentException, FileNotFoundException. All unchecked.