Checked and Unchecked Exceptions
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…
A Failure With No Predefined Name
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.
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. As you read, pay attention to a single decision: the class you extend determines whether the compiler forces the code that uses it to handle the error. That decision is the heart of the topic.
What do you think happens?
If ParseError extends Exception, and a method throws a ParseError without declaring it or catching it, will the program compile?
Reveal answer
No, the compiler requires a throws clause or a catch
Extending Exception makes ParseError a checked exception. A method that throws a checked exception must either catch it or declare it in a throws clause, and the compiler enforces this before the program runs.
Following a Throw Through the Call Stack
A throw statement creates an exception object and hands it to the runtime. Once that happens, normal execution stops at the throw. The runtime looks for a try statement that catches the error. Consider a small chain of method calls: main calls process, and process calls checkValue. Suppose checkValue throws a ParseError and neither process nor checkValue has a catch for it.
In the diagram, the frames for process and checkValue are the ones that the exception leaves behind. Execution does not return to the line after the call that failed. Instead it resumes at the first enclosing try statement that catches a ParseError, which here is in main. This is why a checked exception's throws clause matters: it tells the compiler, and the reader, which methods in that chain may let the failure escape.
Where Custom Exceptions Sit in the Hierarchy
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. Throwable is the root of Java's exception hierarchy, so every exception object, custom or predefined, is some kind of Throwable.
Read the diagram from the top. Every exception class descends from Throwable. Beneath it, the branch that matters for this topic starts at Exception. Classes that extend Exception directly, such as ParseError, are checked. Classes that extend RuntimeException, a subclass of Exception, are unchecked. The diagram places Error beside Exception as another subclass of Throwable; this article concentrates on the Exception branch, because that is where the choice you make when writing a custom class takes effect.
Writing the ParseError Class
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 constructor calls super(message), which passes the string to the constructor of Exception. Exception stores the message, so the class works without any message-handling code of its own.
Because the class inherits from its superclass, it still has access to getMessage() and printStackTrace(). 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
A throw statement uses the constructor to create the exception object. 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. The example below follows the pattern described in the source: a method checks its argument, throws a ParseError when the value is negative, and declares that it can do so. The main method calls the method inside a try statement and catches the error.
negative value: -4- 1
checkValuereceives -4, so the if condition is true and the throw statement runs. - 2The constructor passes the message to super, so
getMessage()later returns that text. - 3Because
checkValuedeclares throwsParseError, the compiler accepts it without a catch insidecheckValue. - 4main wraps the call in try, so the catch clause receives the exception and prints its message.
- throws
ParseErrorin the heading declares the checked exception - the throw statement builds the message from a variable
Choosing the Base Class
The base class is the single decision that controls whether the compiler requires handling. If the programmer wants mandatory handling, the new class extends Exception or one of Exception's other subclasses. If the programmer does not want to force callers into mandatory handling, the new class extends RuntimeException or one of its subclasses. The decision should be made deliberately when the class is written, because it carries a rule about compile-time checking.
public class ParseError extends Exception {
public ParseError(String message) {
super(message);
}
}
// caller must handle it
static void checkValue(int value) throws ParseError {
if (value < 0) {
throw new ParseError("negative value: " + value);
}
}ParseError extends Exception, so it is checked. The method must declare throws ParseError or catch it.
public class ParseError extends RuntimeException {
public ParseError(String message) {
super(message);
}
}
// no throws clause needed
static void checkValue(int value) {
if (value < 0) {
throw new ParseError("negative value: " + value);
}
}ParseError extends RuntimeException, so it is unchecked. The throw is allowed without a throws clause, and the constructor and throw statement are unchanged.
| Question | Extends Exception (checked) | Extends RuntimeException (unchecked) |
|---|---|---|
| Throw statement form | throw new ParseError(message) | throw new ParseError(message) |
| Throws clause required | Yes, unless caught inside the method | No |
| Caller must catch or declare | Yes | No |
| Rule checked by | The compiler, before the program runs | Not enforced at compile time |
Same constructor and throw, different base class
Adding Fields to Carry Extra Data
An error message is not always enough. Sometimes the code that catches an exception needs details about the situation that caused it, such as coordinates or a related object. Adding fields to a custom exception class is the way to carry that information along with the error. The extra fields are ordinary fields, so they are set in the constructor, and a catch block can read them with normal field access once it has the exception object.
The ShipDestroyed object holds an error message together with information about a ship that was destroyed. Because it extends RuntimeException, a method that throws it does not need a throws clause. The message still reaches the catch block through super(message), while the extra fields are set by the constructor body. Notice that the constructor does two jobs: it hands the message upward and it records the data the class exists to carry.
Mistakes With Custom Exception Classes
Common mistake Extending a class that is not Throwable or one of its subclasses
Why it failsA custom exception class must extend Throwable or one of its subclasses, because Throwable is the root of the exception hierarchy.
Do this insteadExtend Exception or RuntimeException, or another subclass of Throwable.
Common mistake Extending Exception and then throwing without a throws clause or catch
What it looks likepublic class ParseError extends Exception { ... } followed by a method that executes throw new ParseError("bad") with no throws clauseWhy it failsExtending Exception makes the class checked, so the compiler requires every method that throws it to declare or catch it.
Do this insteadAdd throws ParseError to the method heading, or catch the error inside the method. Alternatively, extend RuntimeException if mandatory handling is not wanted.
Common mistake Choosing the base class without deciding on checking
Why it failsThe base class carries a rule about compile-time checking. Choosing it by accident changes what every caller must do.
Do this insteadDecide deliberately when writing the class whether callers should be forced to handle the failure.
Common mistake Forgetting super(message) in the constructor
Why it failsThe message is passed to Exception through
super(message). Without it, the inheritedgetMessage()does not receive the text the programmer wanted to report.Do this insteadCall super(message) as the first statement of the constructor.
Common mistake Adding extra fields but never setting them in the constructor, so the catch block reads default values instead of the real data
Trying It Yourself
Write a custom exception class called InvalidWord that extends RuntimeException. Give it a constructor that takes a message and a String field named word, and set that field in the constructor. Then write a method that throws an InvalidWord when a string is empty, without declaring a throws clause. Explain in one sentence why the method compiles without the throws clause.
Hints
- Use
super(message)first in the constructor, then assign the word field. - The base class
RuntimeExceptionmakes the exception unchecked, so the compiler does not require a throws clause. - Trace the call stack: if the throw is not caught in the same method, the caller must catch it or the program ends with an unhandled exception, but the compiler does not force a declaration.
Key Takeaways
- A custom exception class must extend Throwable or one of its subclasses, and it is usually written to name a failure that no predefined class describes well.
- Extending Exception produces a checked exception, so throwing methods must declare it with a throws clause or catch it; extending
RuntimeExceptionproduces an unchecked exception that needs no mandatory handling. - The constructor passes its message to
super(message), which lets the inheritedgetMessage()return that text to the catch block. - Extra data such as coordinates or a related object is stored by adding fields to the class and setting them in the constructor.
- The base class is the deliberate decision that controls compile-time checking, and the throw statement and constructor look identical either way.