Skip to content
Theory · Exceptions · lesson 6 of 12

Checked and unchecked

Our building has a fire inspector. He does not wait for a fire but checks the plan in advance. For every alarm that can be foreseen, a floor must have someone who will answer it, or a sign saying "we pass it upstairs". Without that, the building is not approved.

In Java the compiler plays the inspector. The method load below loads a file. A real program would check the disk; for the example, we assume there is no file missing.txt:

JAVA
public class Main {
    static void load(String name) {
        if (name.equals("missing.txt")) {
            throw new Exception("no such file: " + name); // ✗
        }
        System.out.println("Loaded " + name);
    }

    public static void main(String[] args) {
        load("notes.txt");
    }
}
TEXT
error: unreported exception Exception; must be caught or declared to be thrown

The line with the error throws an exception itself. new Exception("no such file: " + name) creates an object of the class Exception with a message, and the throw statement throws it, the same way the JVM threw an ArithmeticException.

The program does not compile, although load("notes.txt") would never throw this exception. The compiler says: the exception Exception is not handled, it must be caught or declared. The inspector does not care whether a fire will break out; he checks the plan.

Yet the division by zero in average from the earlier lessons compiled without any plan. Why do some exceptions need a plan and others do not? The answer is in the tree of exception classes.

The exception tree

Exception classes are linked by inheritance, like any other classes:

TEXT
Throwable
├── Error
│   └── StackOverflowError
└── Exception
    ├── RuntimeException
    │   ├── ArithmeticException
    │   ├── NullPointerException
    │   ├── IllegalArgumentException
    │   │   └── NumberFormatException
    │   └── …
    └── IOException

What each branch means:

  • Throwable: the root, everything that can be thrown with throw;
  • Error: failures of the JVM itself, such as StackOverflowError from recursion that goes too deep; they are usually not caught, a program cannot cope with them;
  • Exception: situations a program can respond to;
  • RuntimeException: a branch inside Exception for errors in the code itself: division by zero, null, a wrong argument;
  • IOException: input and output errors, such as a file missing from the disk or a dropped network.

All the exceptions you know, from ArithmeticException to NumberFormatException, grow from the RuntimeException branch.

Two groups of exceptions

Checked exceptions are Exception and its subclasses, except the RuntimeException branch. For them the compiler demands a plan: each must be caught or declared. These are situations the programmer does not control, like a missing file, so you have to be ready for them in advance. A real loading method would throw an IOException.

Unchecked exceptions are the RuntimeException and Error branches. For them the compiler demands nothing. They point to an error in the code, and the right response is to fix the code: do not divide by zero, check for null.

This question comes up in almost every Java interview. The typical wrong answer is "RuntimeException is checked and needs throws". Whether an exception is checked depends on where its class sits in the tree, not on how serious the error is.

Catch or declare

If a checked exception may fly out inside a method, the method has two ways.

Catch it

Wrap the risky code in a try with a matching catch. Then the exception does not leave the method, and its callers have nothing to do.

Declare it with throws

Write the word throws and the exception's class in the method header. This is the "we pass it upstairs" sign: the method does not answer the alarm itself and warns everyone who calls it.

JAVA
static void load(String name) throws Exception {
    if (name.equals("missing.txt")) {
        throw new Exception("no such file: " + name);
    }
    System.out.println("Loaded " + name);
}

Now whoever calls load needs a plan. The method main catches the exception:

JAVA
try {
    load("notes.txt"); // Loaded notes.txt
    load("missing.txt");
} catch (Exception e) {
    System.out.println(e.getMessage()); // no such file: missing.txt
}

main has a second way too: declare it as well, public static void main(String[] args) throws Exception. Then the exception, once it reaches main, stops the program with a stack trace, like an uncaught one.

Here the inspector picture is imprecise. A real inspector is satisfied with one person in charge for the whole building. The compiler demands a decision on every floor the exception may pass through: each method either catches it or declares throws.

Common mistake

To make the compiler quiet, the exception is caught with an empty catch:

JAVA
try {
    load("missing.txt");
} catch (Exception e) { // ✗
}
System.out.println("Done");

The program compiles and prints Done, as if all were well. But the file was not loaded, and the message no such file: missing.txt vanished without a trace. An empty catch is a person who switched off the alarm and walked away. If you catch an exception, respond to it: at least print e.getMessage(), and better, fix the situation.

Key points

  • Throwable splits into Error and Exception, and inside Exception there is the RuntimeException branch.
  • Checked exceptions are Exception without the RuntimeException branch; unchecked ones are RuntimeException, Error and their subclasses.
  • A method must catch a checked exception in try/catch or declare it with throws, or the program does not compile.
  • An empty catch hides an error: an exception you catch must be handled.