Creating Custom Exception Classes
Exceptions whose classes are not subclasses of Error or RuntimeException are checked exceptions, and the compiler requires the program to handle them.
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 an Exception Must Be Handled
Many programmers arrive at Java with a reasonable but incomplete picture of exceptions. An exception is a signal that something went wrong, and a program can respond to that signal with a try statement and a catch clause. From there it is tempting to conclude that exception handling is a matter of taste: you catch the problems you expect and let the rest go. That picture is accurate for some exceptions and dangerously wrong for others.
The language distinguishes between exceptions that a programmer may choose to handle and exceptions that the program must handle. Getting that distinction right is what separates code that compiles from code that the compiler rejects. This article looks at the cases where the compiler refuses to let you take the optional route, and then shows how to create your own exception classes that take part in the same rules.
Checked and Optional Exceptions
A checked exception is an exception whose class is not a subclass of Error or RuntimeException. The compiler requires the program to handle every checked exception it can throw.
Notice that the definition is about class hierarchy, not about how serious the problem is. The category is defined by where the exception class sits in Java's family of exception classes. Once you know that, the rule follows: a class outside the Error and RuntimeException branches is a checked exception, and checked exceptions carry the mandatory requirement.
Exceptions in the other group behave differently. A routine can throw an IllegalArgumentException without announcing the possibility, and the calling program is free either to catch that exception or to ignore it. The same freedom applies to NullPointerException. Declaring that a subroutine can throw an IllegalArgumentException is therefore a courtesy to readers of the code, not a compiler requirement.
Checked exceptions carry the compiler's mandatory requirement. Errors and RuntimeExceptions, including IllegalArgumentException and NullPointerException, are optional to handle.
Why Input and Output Forces the Issue
Checked exceptions generally represent conditions outside the control of the programmer, such as bad input or an illegal action taken by the user. Such errors cannot be prevented by writing better code alone, so a robust program has to be ready for them. Java's design makes it impossible for programmers to ignore the possibility of these errors.
Input and output is where data from outside the program arrives, and that data may be malformed or unavailable. Several checked exceptions can occur when using Java's input and output routines, which is why you cannot use these routines comfortably without understanding exception handling first.
Two Ways to Satisfy a Checked Exception
Suppose a statement inside a subroutine can generate a checked exception. That statement might be a throw statement that throws the exception directly, or it might be a call to another subroutine that can throw it. Either way the exception must be handled, and there are two ways to do it. The subroutine can handle the exception itself with a try statement and a catch clause, which stops the chain at that point. Or it can announce the exception in its heading with a throws clause, which passes the obligation to whichever caller invokes it.
The diagram shows the chain for the version where the exception is raised inside readRecord and the heading announces it. The exception leaves readRecord and reaches main, where the try statement with its catch clause stops it. If main had no try statement, the requirement would keep passing upward, and the compiler would reject a main method that neither caught the exception nor declared it.
Defining a Custom Exception Class
Suppose a program is reading text and finds a piece of it that breaks the expected format. The program cannot repair the problem on the spot, so it needs to signal the failure by throwing an exception. Java already provides many predefined exception classes, 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.
A custom exception class must extend Throwable or one of its subclasses. It is usually built to represent a failure that no predefined class describes well.
- extends Exception makes
ParseErrora checked exception. - The constructor takes a message string and passes it to
super(message). - There are no extra fields or methods; the class exists so its name identifies the failure.
The ParseError class has no fields and no methods of its own. The call super(message) hands the string to the constructor of Exception, which stores it. That is why the class works without writing any message-handling code of its own. The class still has access to getMessage() and printStackTrace(), because it inherits them from its superclass. Calling getMessage() on a ParseError object returns whatever message the constructor received. 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 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 value.
static int parseCount(String text) throws ParseError {
if (text.startsWith("-")) {
throw new ParseError("negative value: " + text);
}
return Integer.parseInt(text);
}- 1The throws
ParseErrorclause in the heading is required, becauseParseErroris checked and this throw is not inside a try statement that catches it. - 2The if test checks the argument for a leading minus sign.
- 3The throw statement creates a new
ParseError, building its message from the offending text, and hands it to the runtime. - 4When the throw runs, the statements after it are skipped and the runtime searches for a matching handler.
- 5If the test fails, the method returns the parsed number normally.
The throw statement does not return a value to the place where it appears. Control leaves the current statement sequence immediately. The runtime then looks for a catch clause whose type matches the exception. The flowchart shows that the statements after the throw are the ones that get skipped, and that the handler that runs next is the matching catch clause, if one exists.
Choosing the Base Class
A new class can extend Throwable directly or extend any of its subclasses. In practice, the choice that matters is which subclass to extend. 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 base class therefore carries a rule about compile-time checking, and the decision should be made deliberately when the class is written.
| Base class | Status of the custom class | Does a throwing method need a throws clause? |
|---|---|---|
| Exception | Checked | Yes, unless the throw is inside a try that catches it |
| RuntimeException | Unchecked | No |
How the base class decides whether the compiler requires handling
The base class is the single decision that controls whether the compiler requires handling. If the class extends Exception, its instances are checked and every method that throws them must declare or catch them. If the class extends RuntimeException, the same throw is allowed without a throws clause, because the exceptions are not checked at compile time. The throw statement and the constructor look identical in both cases. Only the superclass changes.
What do you think happens?
If ParseError extends Exception and parseCount is called from main with no try statement and no throws clause on main, does the program compile?
Reveal answer
No. ParseError is checked, so the compiler rejects main unless it catches the error or declares it with a throws clause.
The throw inside parseCount is allowed only because parseCount declares throws ParseError. The obligation then passes to main, which must handle it.
Carrying Extra Data in an Exception
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. Extra data such as coordinates or a related object can be stored by adding fields to the custom class and setting them in the constructor.
- extends
RuntimeExceptionmeans a method that throws ShipDestroyed needs no throws clause. - The constructor sends the message to
super(message), sogetMessage()still works. - The row and col fields are set in the constructor and read by ordinary getter methods.
The extra fields are ordinary fields. Once a catch block has the exception object, it can read them with normal field access, here through getRow() and getCol(). The custom fields are set when the object is created, inside the constructor, and they travel with the exception object to whichever caller catches it.
Mistakes When Creating Custom Exceptions
Common mistake Assuming a Java program may simply ignore any exception it likes
Why it failsChecked exceptions are required to be handled. Ignoring one with no try statement and no throws clause produces a compile-time rejection.
Do this insteadIdentify whether the exception is checked by its class hierarchy, then either catch it or declare it with a throws clause.
Common mistake Extending Exception when you wanted optional handling
What it looks likepublic class ParseError extends Exception with callers that have no handler
Why it failsExtending Exception makes every method that throws
ParseErrorresponsible for handling it.Do this insteadExtend RuntimeException instead if callers should not be forced into mandatory handling.
Common mistake Writing a custom exception class with no constructor that passes the message
Why it failsIf the constructor does not call
super(message), the message is not stored andgetMessage()cannot return it.Do this insteadAccept a message parameter and pass it to super(message).
Common mistake Expecting code after a throw statement to run
Why it failsControl leaves the current statement sequence as soon as the throw runs, so the statements after it are skipped.
Practice: Predict the Compiler's Response
A class NumberFormatProblem extends IllegalArgumentException. A method convert(String s) throws NumberFormatProblem and throws it when s is empty. Main calls convert with no try statement and no throws clause. Will the program compile, and why?
Hints
- Look at the superclass chain:
IllegalArgumentExceptionbelongs to theRuntimeExceptionbranch. - Ask whether a throws clause in the heading of convert is enough by itself, and what main must do if the exception is unchecked.
Key Takeaways
- A checked exception is one whose class is not a subclass of Error or
RuntimeException, and the compiler requires the program to handle it. - A checked exception is handled either with a try statement and catch clause, or announced with a throws clause that passes the obligation to callers.
- A custom exception class extends Throwable or one of its subclasses; extending Exception makes it checked, while extending
RuntimeExceptionmakes it unchecked and optional to handle. - A custom exception passes its message to
super(message)and can carry extra data stored in fields set by its constructor.