Concepts / Checked and Unchecked Exceptions

Checked and Unchecked Exceptions

A custom exception class must extend Throwable or one of its subclasses, and it is usually built to represent a failure that no predefined class describes well.

  • Programming
Interactive lab

Try it: try / except / else / finally

How an exception jumps out of a try block to the first matching except clause, when else runs, and why finally always runs.

How it works

  1. Statements in try run until one raises an exception; the rest of try is skipped.
  2. Python checks the except clauses in order and runs the first one whose type matches.
  3. If nothing was raised, the else block runs instead.
  4. finally runs in every case.

Default run (9 steps): Call safe_divide('4'). … Return 25.0. Output was: Worked: 25.0 / Done.

Simplified: One fixed function; int() and / are modelled exactly (including how Python prints floats), but nothing is executed as Python.

Educational simulation

Loading the simulation…

A Failure With No Predefined Name

Imagine a program that reads text and finds a piece of it that breaks the expected format. The program cannot repair the problem on the spot, so it has to signal the failure by throwing an exception. Java already provides many predefined exception classes, such as IllegalArgumentException and IOException, and one of them is often a reasonable choice. Sometimes, though, none of the predefined classes describes the situation accurately. In that case the programmer writes a new exception class that names the exact kind of failure.

This article follows one example from start to finish: a custom exception called ParseError that signals a string which does not have the expected form. As you read, pay attention to a single decision: the class you extend determines whether the compiler forces the code that uses it to handle the error. That decision is the heart of the topic.

What do you think happens?

If ParseError extends Exception, and a method throws a ParseError without declaring it or catching it, will the program compile?

  • Yes, the compiler accepts it

  • No, the compiler requires a throws clause or a catch

Reveal answer
Answer:

No, the compiler requires a throws clause or a catch

Extending Exception makes ParseError a checked exception. A method that throws a checked exception must either catch it or declare it in a throws clause, and the compiler enforces this before the program runs.

Following a Throw Through the Call Stack

A throw statement creates an exception object and hands it to the runtime. Once that happens, normal execution stops at the throw. The runtime looks for a try statement that catches the error. Consider a small chain of method calls: main calls process, and process calls checkValue. Suppose checkValue throws a ParseError and neither process nor checkValue has a catch for it.

mainhas try and catchParseErrorprocesscalls checkValue, no catchcheckValuethrows ParseErrorcallscallsexception leavesexception leaves
When checkValue throws a ParseError, which frames are abandoned, and where does execution resume if a catch exists only in main?

In the diagram, the frames for process and checkValue are the ones that the exception leaves behind. Execution does not return to the line after the call that failed. Instead it resumes at the first enclosing try statement that catches a ParseError, which here is in main. This is why a checked exception's throws clause matters: it tells the compiler, and the reader, which methods in that chain may let the failure escape.

Where Custom Exceptions Sit in the Hierarchy

Definition

A custom exception class must extend Throwable or one of its subclasses, and it is usually built to represent a failure that no predefined class describes well. Throwable is the root of Java's exception hierarchy, so every exception object, custom or predefined, is some kind of Throwable.

Throwableroot of the hierarchyExceptionchecked branchRuntimeExceptionunchecked branchShipDestroyedcustom, uncheckedErrorother branchParseErrorcustom, checkedextendsextendsextendsextendsextends
Where do Exception, RuntimeException, Error, and the custom classes sit under Throwable, and which branch makes handling mandatory?

Read the diagram from the top. Every exception class descends from Throwable. Beneath it, the branch that matters for this topic starts at Exception. Classes that extend Exception directly, such as ParseError, are checked. Classes that extend RuntimeException, a subclass of Exception, are unchecked. The diagram places Error beside Exception as another subclass of Throwable; this article concentrates on the Exception branch, because that is where the choice you make when writing a custom class takes effect.

Writing the ParseError Class

The ParseError class has no fields and no methods of its own. It holds only a constructor, and that constructor takes a message string. The constructor calls super(message), which passes the string to the constructor of Exception. Exception stores the message, so the class works without any message-handling code of its own.

java

Because the class inherits from its superclass, it still has access to getMessage() and printStackTrace(). Calling e.getMessage() on a ParseError object returns whatever message the constructor received. The main point of the class is simply to exist. Its name tells a reader which kind of failure occurred, and that name is what a catch clause matches against.

Throwing a ParseError

A throw statement uses the constructor to create the exception object. The constructor must receive an error message, as in throw new ParseError with a string argument. The message can be a fixed string or one built from variables, such as a string concatenation that includes the offending value. The example below follows the pattern described in the source: a method checks its argument, throws a ParseError when the value is negative, and declares that it can do so. The main method calls the method inside a try statement and catches the error.

java
Output
negative value: -4
Walk through it
  1. 1
    checkValue receives -4, so the if condition is true and the throw statement runs.
  2. 2
    The constructor passes the message to super, so getMessage() later returns that text.
  3. 3
    Because checkValue declares throws ParseError, the compiler accepts it without a catch inside checkValue.
  4. 4
    main wraps the call in try, so the catch clause receives the exception and prints its message.
Notes on the code
  • throws ParseError in the heading declares the checked exception
  • the throw statement builds the message from a variable

Choosing the Base Class

The base class is the single decision that controls whether the compiler requires handling. If the programmer wants mandatory handling, the new class extends Exception or one of Exception's other subclasses. If the programmer does not want to force callers into mandatory handling, the new class extends RuntimeException or one of its subclasses. The decision should be made deliberately when the class is written, because it carries a rule about compile-time checking.

Extends Exception?Checked exceptioncallers must catch ordeclareExtendsRuntimeException?Unchecked exceptionno throws clause requiredyesnoyes
Given a custom exception class, what sequence of checks determines whether the compiler forces callers to catch or declare it?
Before
public class ParseError extends Exception {
    public ParseError(String message) {
        super(message);
    }
}

// caller must handle it
static void checkValue(int value) throws ParseError {
    if (value < 0) {
        throw new ParseError("negative value: " + value);
    }
}

ParseError extends Exception, so it is checked. The method must declare throws ParseError or catch it.

After
public class ParseError extends RuntimeException {
    public ParseError(String message) {
        super(message);
    }
}

// no throws clause needed
static void checkValue(int value) {
    if (value < 0) {
        throw new ParseError("negative value: " + value);
    }
}

ParseError extends RuntimeException, so it is unchecked. The throw is allowed without a throws clause, and the constructor and throw statement are unchanged.

QuestionExtends Exception (checked)Extends RuntimeException (unchecked)
Throw statement formthrow new ParseError(message)throw new ParseError(message)
Throws clause requiredYes, unless caught inside the methodNo
Caller must catch or declareYesNo
Rule checked byThe compiler, before the program runsNot enforced at compile time

Same constructor and throw, different base class

Adding Fields to Carry Extra Data

An error message is not always enough. Sometimes the code that catches an exception needs details about the situation that caused it, such as coordinates or a related object. Adding fields to a custom exception class is the way to carry that information along with the error. The extra fields are ordinary fields, so they are set in the constructor, and a catch block can read them with normal field access once it has the exception object.

java

The ShipDestroyed object holds an error message together with information about a ship that was destroyed. Because it extends RuntimeException, a method that throws it does not need a throws clause. The message still reaches the catch block through super(message), while the extra fields are set by the constructor body. Notice that the constructor does two jobs: it hands the message upward and it records the data the class exists to carry.

Mistakes With Custom Exception Classes

  • Common mistake

    Extending a class that is not Throwable or one of its subclasses

    Why it fails

    A custom exception class must extend Throwable or one of its subclasses, because Throwable is the root of the exception hierarchy.

    Do this instead

    Extend Exception or RuntimeException, or another subclass of Throwable.

  • Common mistake

    Extending Exception and then throwing without a throws clause or catch

    What it looks like
    public class ParseError extends Exception { ... } followed by a method that executes throw new ParseError("bad") with no throws clause
    Why it fails

    Extending Exception makes the class checked, so the compiler requires every method that throws it to declare or catch it.

    Do this instead

    Add throws ParseError to the method heading, or catch the error inside the method. Alternatively, extend RuntimeException if mandatory handling is not wanted.

  • Common mistake

    Choosing the base class without deciding on checking

    Why it fails

    The base class carries a rule about compile-time checking. Choosing it by accident changes what every caller must do.

    Do this instead

    Decide deliberately when writing the class whether callers should be forced to handle the failure.

  • Common mistake

    Forgetting super(message) in the constructor

    Why it fails

    The message is passed to Exception through super(message). Without it, the inherited getMessage() does not receive the text the programmer wanted to report.

    Do this instead

    Call super(message) as the first statement of the constructor.

  • Common mistake

    Adding extra fields but never setting them in the constructor, so the catch block reads default values instead of the real data

Trying It Yourself

MEDIUM

Write a custom exception class called InvalidWord that extends RuntimeException. Give it a constructor that takes a message and a String field named word, and set that field in the constructor. Then write a method that throws an InvalidWord when a string is empty, without declaring a throws clause. Explain in one sentence why the method compiles without the throws clause.

Hints
  • Use super(message) first in the constructor, then assign the word field.
  • The base class RuntimeException makes the exception unchecked, so the compiler does not require a throws clause.
  • Trace the call stack: if the throw is not caught in the same method, the caller must catch it or the program ends with an unhandled exception, but the compiler does not force a declaration.

Key Takeaways

  • A custom exception class must extend Throwable or one of its subclasses, and it is usually written to name a failure that no predefined class describes well.
  • Extending Exception produces a checked exception, so throwing methods must declare it with a throws clause or catch it; extending RuntimeException produces an unchecked exception that needs no mandatory handling.
  • The constructor passes its message to super(message), which lets the inherited getMessage() return that text to the catch block.
  • Extra data such as coordinates or a related object is stored by adding fields to the class and setting them in the constructor.
  • The base class is the deliberate decision that controls compile-time checking, and the throw statement and constructor look identical either way.