Exceptions in Java: Hierarchy and Types

Errors, checked exceptions and unchecked exceptions sit in one hierarchy, and knowing which is which decides how you should react.

What an exception is

An exception is an object describing an abnormal condition. When one is thrown, normal execution stops and the JVM searches up the call stack for a handler. If none is found, the thread ends and the stack trace is printed.

The hierarchy

Throwable
  |
  +-- Error                    serious problems, do not catch
  |     OutOfMemoryError
  |     StackOverflowError
  |
  +-- Exception                application problems
        |
        +-- RuntimeException   unchecked
        |     NullPointerException
        |     IllegalArgumentException
        |     IllegalStateException
        |     ArithmeticException
        |     IndexOutOfBoundsException
        |     ClassCastException
        |     NumberFormatException
        |
        +-- IOException        checked
        +-- SQLException       checked
        +-- (your own checked exceptions)

Everything that can be thrown is a Throwable. The split that matters day to day is between Error, RuntimeException and everything else under Exception.

Errors

// Do not do this
try {
    deepRecursion();
} catch (StackOverflowError e) {
    // the stack is already unreliable; continuing is not meaningful
}

An Error signals a condition the application cannot sensibly recover from. Catch one only at the very top of a program to log it before shutting down, and never to carry on as if nothing happened.

Checked exceptions

public String readFirstLine(Path path) throws IOException {   // declared
    try (BufferedReader reader = Files.newBufferedReader(path)) {
        return reader.readLine();
    }
}

A checked exception must be either caught or declared with throws. The compiler enforces it. The intent is that these represent conditions a correct program should anticipate, such as a missing file or a dropped connection.

Unchecked exceptions

public double divide(int a, int b) {
    if (b == 0) {
        throw new IllegalArgumentException("Divisor must not be zero");
    }
    return (double) a / b;
}

Anything extending RuntimeException needs no declaration and no handler. These usually indicate a programming mistake: a null that should not be null, an argument that was never valid, an index outside the array.

Comparison

AspectCheckedUnchecked
SuperclassException, not RuntimeExceptionRuntimeException
Compiler enforcedYesNo
Must be declaredYesNo
Typically meansAn expected external failureA bug in the code
ExampleIOExceptionNullPointerException
Checked exceptions are a design decision unique to Java, and a contested one. The consensus in modern Java is to use them sparingly: only when the caller can realistically do something different in response. Otherwise an unchecked exception keeps signatures honest.

The exceptions you will meet most

ExceptionTypical cause
NullPointerExceptionCalling a method on a null reference
ArrayIndexOutOfBoundsExceptionAn index below zero or at or beyond the length
NumberFormatExceptionInteger.parseInt on text that is not a number
ClassCastExceptionCasting to a type the object is not
IllegalArgumentExceptionAn argument outside its allowed range
IllegalStateExceptionA method called at the wrong time
ConcurrentModificationExceptionStructurally changing a collection while iterating
ArithmeticExceptionInteger division by zero

Helpful NullPointerException messages

Cannot invoke "String.length()" because the return value of
"Note.title()" is null

Since Java 14 the runtime describes exactly which reference was null. This turned the least informative exception in Java into one of the most useful, and it is on by default from Java 15.

Reading a stack trace

Exception in thread "main" java.lang.NumberFormatException: For input string: "ten"
    at java.base/java.lang.Integer.parseInt(Integer.java:652)
    at com.example.notes.Importer.parseRow(Importer.java:41)   <-- your code
    at com.example.notes.Importer.run(Importer.java:22)
    at com.example.notes.Main.main(Main.java:9)
Caused by: ...

Read the top line for the type and message, then scan down for the first frame in your own code. The Caused by section, when present, holds the original failure and is usually the more informative part.

Exceptions cost more than a return

Creating an exception captures the stack trace, which is not free. That is a reason to avoid using exceptions for ordinary control flow, not a reason to avoid them for genuine failures.

Common mistakes

  • Catching Exception or Throwable broadly and hiding real problems.
  • Treating every failure as checked, which pushes throws clauses through the whole codebase.
  • Catching NullPointerException instead of checking for null.
  • Using exceptions to signal an ordinary outcome, such as "not found".
  • Catching Error and continuing.

Best practices

  • Throw the most specific type that describes the problem.
  • Use unchecked exceptions for programming errors and invalid arguments.
  • Reserve checked exceptions for conditions a caller can genuinely handle differently.
  • Include the offending value in the message, never the whole object if it holds secrets.
  • Let an exception travel to a layer that can actually decide what to do.

Practice

  1. Classify each as checked, unchecked or error: IOException, ArithmeticException, OutOfMemoryError, SQLException, ClassCastException.
  2. Why does the compiler insist on handling IOException but not NullPointerException?
  3. Write a method that throws IllegalArgumentException for a negative age, with a useful message.
  4. Read a stack trace from your own code and identify the first line you wrote.
  5. Give one case where a checked exception is the right choice and one where it is not.

Conclusion

Errors are not yours to handle, unchecked exceptions usually mean a bug, and checked exceptions mean a failure the caller was expected to plan for. Choosing correctly is most of good exception design.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.