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.
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
- Statements in try run until one raises an exception; the rest of try is skipped.
- Python checks the except clauses in order and runs the first one whose type matches.
- If nothing was raised, the else block runs instead.
- 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.
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?
Reveal 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.
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
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.
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.
class ParseError extends Exception {
ParseError(String message) {
super(message);
}
}- 1The class header declares
ParseErroras a subclass of Exception, so it is a kind of Throwable. - 2The constructor accepts one String named message. This is the only piece of code the class needs.
- 3
super(message)calls the constructor of Exception, which keeps the text so thatgetMessage()can later return it.
- extends Exception makes
ParseErrora 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.
8
Caught: negative value: -3- The throws clause in the method heading is required because
ParseErroris 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.
| Question | Extends Exception | Extends RuntimeException |
|---|---|---|
| Checked at compile time? | Yes | No |
| Throwing method must declare or catch? | Yes, with a throws clause or a catch | No mandatory handling |
| Throw statement and constructor look the same? | Yes | Yes |
| Example from this article | ParseError | ShipDestroyed |
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.
hit at 2,5
row=2 col=5- 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 failsA 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 insteadExtend Exception for mandatory handling or RuntimeException for unchecked behavior.
Common mistake Extending Exception without adding a throws clause
What it looks likestatic int parseValue(int value) { throw new ParseError("bad"); } fails to compile because ParseError is checked.Why it failsChecked exceptions must be declared with a throws clause or caught inside the method that throws them.
Do this insteadAdd throws ParseError to the method heading, or catch the error inside the method.
Common mistake Forgetting to pass the message to super
Why it failsThe message is stored by the constructor of Exception. If the constructor does not call
super(message),getMessage()has no text to return.Do this insteadCall super(message) as the first statement of the constructor.
Common mistake Assuming the code after a throw runs
What it looks likeAfter throw new ParseError(...), the next println does not execute.
Why it failsA throw statement leaves the method immediately, so the remaining statements in that method are skipped.
Do this insteadPlace 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
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
RuntimeExceptionremoves the need for a throws clause - Adding throws BadRecord to the heading also satisfies the compiler
Key Takeaways
- 1A custom exception extends Throwable or one of its subclasses, and it usually represents a failure that no predefined class describes well.
- 2The constructor passes its message to
super(message), sogetMessage()can return that text in the catch block. - 3Extending Exception makes the class checked, so throwing methods must declare it with a throws clause or catch it. Extending
RuntimeExceptionmakes it unchecked, with no mandatory handling. - 4Extra 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), sogetMessage()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
RuntimeExceptionmakes 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.