Concepts / Predefined Java Exception Classes

Predefined Java Exception Classes

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…

When Built-In Exceptions Fall Short

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.

What do you think happens?

Before reading on: if you write a new exception class to signal a malformed string, what do you think the compiler will require from a method that throws it?

  • Nothing at all, the method can throw it freely

  • The method must declare the exception in a throws clause or catch it

  • The method must be static

Reveal answer
Answer:

It depends on the base class. If the new class extends Exception, the method must declare it with a throws clause or catch it. If the new class extends RuntimeException, no throws clause is required.

The base class you extend is the decision that controls whether the compiler enforces handling. The rest of this article traces that decision through one example.

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. You will see how the class is defined, how it is thrown, and how the choice of base class changes what the compiler requires from the code that uses it.

Tracing a Failure Up the Call Stack

Picture a small program in which main calls a method named parseValue, and parseValue calls nothing else. parseValue checks its argument. When the argument is invalid, parseValue creates a ParseError and throws it. A throw statement creates an exception object and hands it to the runtime, so the method does not return normally. Control leaves parseValue immediately, and the exception travels back to the code that called it, which is main.

maincalls parseValue inside tryparseValuethrows ParseErrorParseError objectcarries the messagecallsthrowpropagates to catch
When parseValue throws a ParseError and nothing inside it catches the error, which calling method is exited next, and where does control end up?

The diagram shows the key idea. Once parseValue throws, its remaining statements are skipped. Execution does not continue inside parseValue, and it does not return a value to main. Instead, the exception moves to the caller. If main wraps the call in a try statement that catches a ParseError, the catch block runs. If nothing catches it, the failure keeps moving outward until the program stops with an error.

Where a Custom Exception Sits in the Hierarchy

Definition

A new exception class must extend the standard class Throwable or one of its subclasses. Throwable is the root of Java's exception hierarchy, so every exception object, custom or predefined, is some kind of Throwable. The choice that matters in practice is which subclass to extend.

Throwableroot of the hierarchyExceptionchecked by defaultParseErrorcustom, extends ExceptionRuntimeExceptionuncheckedIOExceptionpredefinedIllegalArgumentExceptionpredefinedShipDestroyedcustom, extendsRuntimeExceptionextendsextendsextendsextendsextendsextends
Where does a custom exception sit among Throwable's subclasses, and which parent class does it inherit from?

Every custom exception therefore sits below Throwable. Its immediate parent is the class you choose to extend. Because ParseError extends Exception, it inherits Exception's behavior, and because ShipDestroyed extends RuntimeException, it inherits that class's behavior instead. Those two parent classes behave differently with respect to compile-time checking, which the sections below explain.

Writing the ParseError Class

Begin with the class itself. It is short, and most of its content is a single constructor. 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 call super(message) hands the string to the constructor of Exception, which stores it. That is why the class works without any message-handling code of its own.

Code walkthrough
class ParseError extends Exception {
    ParseError(String message) {
        super(message);
    }
}
  1. 1
    The class header declares ParseError as a subclass of Exception, so it is a kind of Throwable.
  2. 2
    The constructor accepts one String named message. This is the only piece of code the class needs.
  3. 3
    super(message) calls the constructor of Exception, which keeps the text so that getMessage() can later return it.
  • extends Exception makes ParseError a checked exception
  • The constructor takes an error message string
  • super(message) passes the string to Exception, which stores it

The class still has access to getMessage() and printStackTrace(), because it inherits them from its superclass. 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 with a throw Statement

A throw statement creates an exception object and hands it to the runtime. 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 word. The example below checks a value and throws when it is negative, then catches the error in main.

java
Output
8
Caught: negative value: -3
Notes on the code
  • The throws clause in the method heading is required because ParseError is checked
  • The concatenation builds a message from the offending value
  • The statement after the failing call never runs

Trace the run carefully. The first call with 4 returns 8 normally, so the first line prints 8. The second call with -3 reaches the throw statement. Control leaves parseValue at that point, so the line that would have printed not reached is skipped. The catch block in main receives the ParseError object, and getMessage() returns the text built in the throw statement.

Choosing Exception or RuntimeException as the Base

The base class is the single decision that controls whether the compiler requires handling. If the programmer does not want to force callers into mandatory exception handling, the new class extends RuntimeException or one of its subclasses. If the programmer does want mandatory handling, the new class extends Exception or one of Exception's other subclasses. The decision should be made deliberately when the class is written.

Checkedmust be declared or caughtParseErrorthrows clause requiredUncheckedno mandatory handlingShipDestroyedno throws clause neededapplies toapplies to
If a class extends Exception instead of RuntimeException, what changes about whether callers must catch or declare the exception?
QuestionExtends ExceptionExtends RuntimeException
Checked at compile time?YesNo
Throwing method must declare or catch?Yes, with a throws clause or a catchNo mandatory handling
Throw statement and constructor look the same?YesYes
Example from this articleParseErrorShipDestroyed

The throw statement and the constructor look identical in both cases. Only the base class changes what the compiler requires.

Carrying Extra Data in Custom Fields

An error message is not always enough. Sometimes the code that catches an exception needs details about the situation that caused it. Adding fields to a custom exception class is the way to carry that information along with the error. Those fields are set in the constructor, next to the call to super(message), so that every thrown instance carries its own data.

java
Output
hit at 2,5
row=2 col=5
Notes on the code
  • row and col are ordinary fields added to the custom class
  • super(message) still supplies the inherited message
  • The catch block reads the fields with normal field access

The ShipDestroyed object holds an error message and information about a ship that was destroyed. Because it extends RuntimeException, the fire method does not need a throws clause. The extra fields are ordinary fields, so the catch block can read them directly once it has the exception object. The data travels with the thrown instance from the throw statement to the handler.

Mistakes Beginners Make with Custom Exceptions

  • Common mistake

    Extending a class that is not part of the Throwable hierarchy

    Why it fails

    A new exception class must extend Throwable or one of its subclasses. Otherwise it is not an exception, and throw cannot be used with it.

    Do this instead

    Extend Exception for mandatory handling or RuntimeException for unchecked behavior.

  • Common mistake

    Extending Exception without adding a throws clause

    What it looks like
    static int parseValue(int value) { throw new ParseError("bad"); } fails to compile because ParseError is checked.
    Why it fails

    Checked exceptions must be declared with a throws clause or caught inside the method that throws them.

    Do this instead

    Add throws ParseError to the method heading, or catch the error inside the method.

  • Common mistake

    Forgetting to pass the message to super

    Why it fails

    The message is stored by the constructor of Exception. If the constructor does not call super(message), getMessage() has no text to return.

    Do this instead

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

  • Common mistake

    Assuming the code after a throw runs

    What it looks like

    After throw new ParseError(...), the next println does not execute.

    Why it fails

    A throw statement leaves the method immediately, so the remaining statements in that method are skipped.

    Do this instead

    Place any cleanup or fallback logic in the catch block that handles the exception.

  • Common mistake

    Storing extra data in static fields instead of instance fields, which makes every thrown instance share the same values

Trace the Base Class Decision

MEDIUM

A class BadRecord is declared as class BadRecord extends Exception, with a constructor that calls super(message). A method load(String line) contains the statement throw new BadRecord("empty line") and has no throws clause. Will this compile? Explain what change would make it compile, and what change would make it compile without any change to the method heading.

Hints
  • Ask whether BadRecord is checked or unchecked, and what that requires from load
  • Changing the base class to RuntimeException removes the need for a throws clause
  • Adding throws BadRecord to the heading also satisfies the compiler

Key Takeaways

  1. 1
    A custom exception extends Throwable or one of its subclasses, and it usually represents a failure that no predefined class describes well.
  2. 2
    The constructor passes its message to super(message), so getMessage() can return that text in the catch block.
  3. 3
    Extending Exception makes the class checked, so throwing methods must declare it with a throws clause or catch it. Extending RuntimeException makes it unchecked, with no mandatory handling.
  4. 4
    Extra data is stored by adding fields to the class and setting them in the constructor, and the catch block reads them as ordinary fields.

At a glance

  • A custom exception extends Throwable or one of its subclasses, and it usually represents a failure that no predefined class describes well.
  • The constructor passes its message to super(message), so getMessage() can return that text in the catch block.
  • Extending Exception makes the class checked, so throwing methods must declare it with a throws clause or catch it. Extending RuntimeException makes it unchecked, with no mandatory handling.
  • Extra data is stored by adding fields to the class and setting them in the constructor, and the catch block reads them as ordinary fields.