An exception ends the normal flow of a program and passes control to a handler, if
one exists. Exceptions are objects of class Throwable or a subclass. Java
checks at compile time that most of them are either handled or declared (JLS
chapter 11, and 14.18 and 14.20).
Kinds of exception
Throwable
├── Error unchecked: serious problems, not normally caught
│ ├── OutOfMemoryError, StackOverflowError (VirtualMachineError)
│ ├── ExceptionInInitializerError, NoClassDefFoundError (LinkageError)
│ └── AssertionError
└── Exception checked
├── IOException, InterruptedException, ReflectiveOperationException, …
└── RuntimeException unchecked
├── NullPointerException, ArithmeticException, ClassCastException
├── IndexOutOfBoundsException, ArrayStoreException, NegativeArraySizeException
├── IllegalArgumentException, IllegalStateException, UnsupportedOperationException
└── MatchException (Java 21)
The unchecked exception classes are RuntimeException,
Error and their subclasses. Every other subclass of
Throwable is checked (JLS 11.1.1). New exception types are
declared as ordinary classes; a generic class may not extend
Throwable.
Exceptions are thrown by a throw statement, by the evaluation of an
expression, or by the virtual machine when something goes wrong inside it:
| Cause | Exception |
|---|---|
Field access, method call, unboxing, synchronized, throw or array access on null | NullPointerException |
| Integer division or remainder by zero | ArithmeticException |
| A cast to a type the object does not have | ClassCastException |
| An array index out of range | ArrayIndexOutOfBoundsException |
| Storing the wrong type into an array | ArrayStoreException |
| A negative array size | NegativeArraySizeException |
| An exhaustive switch or record pattern that matches nothing at run time, or an accessor that throws during pattern matching | MatchException |
| An exception in a static initialiser | ExceptionInInitializerError |
An assert that fails, with assertions enabled | AssertionError |
| No memory for a new object, or too deep a call stack | OutOfMemoryError, StackOverflowError |
Compile-time checking
A checked exception that a method or constructor can throw must either be caught
inside it or listed in its throws clause (JLS 11.2). The rule applies to
every statement, including calls of methods that declare checked exceptions.
String firstLine(Path p) throws IOException { // declares it
try (var reader = Files.newBufferedReader(p)) {
return reader.readLine();
}
}
String firstLineOrEmpty(Path p) {
try {
return firstLine(p);
} catch (IOException e) { // handles it
return "";
}
}
- A
throwsclause may list unchecked exceptions too, as documentation; it has no effect on them. - An overriding method may not throw checked exceptions that the overridden method does not allow.
- A static initialiser, a lambda body, and the initialisers of a class with no explicit constructor cannot throw checked exceptions that nothing handles. A lambda can throw only what its functional interface method declares.
- Catching a checked exception that the
tryblock cannot throw is an error.ExceptionandThrowablemay always be caught, since they also cover unchecked exceptions.
The throw statement
ThrowStatement:
throw Expression ;
The expression must be of a type assignable to Throwable. If it is
null, a NullPointerException is thrown instead. A
throw always completes abruptly, so code after it is unreachable.
try, catch and finally
TryStatement:
try Block Catches
try Block [Catches] Finally
TryWithResourcesStatement
CatchClause:
catch ( {VariableModifier} CatchType Identifier ) Block
CatchType:
UnannClassType {| ClassType}
Finally:
finally Block
- The catch clauses are tried in order and the first whose type matches the exception runs. A clause that can never run because an earlier one catches a superclass is an error.
- The
finallyblock runs whatever happens: after thetryblock completes normally, after a catch clause, or when an exception is not caught, and also onreturn,breakorcontinueout of thetry. - If the
finallyblock itself completes abruptly (withreturn,throw,breakorcontinue), that replaces the earlier outcome, and any exception or return value from the try is lost.javac -Xlint:finallywarns about this. - The catch parameter may be the unnamed
_(Java 22) when it is not used:catch (NumberFormatException _) { … }.
static int f() {
try {
return 1;
} finally {
System.out.println("finally runs before the method returns 1");
}
}
Multi-catch
Since Java 7 one clause can catch several types, separated by |. The
alternatives may not be subclasses of each other. The parameter is implicitly
final, and its type is the least upper bound of the alternatives.
try {
return Integer.parseInt(Files.readString(path).strip());
} catch (IOException | NumberFormatException e) {
log.warning("bad file: " + e);
return 0;
}
Precise rethrow
Since Java 7, when a catch parameter that is final or effectively final is thrown
again, the compiler treats it as throwing only the checked exceptions the
try block can throw, not the parameter’s declared type:
void process() throws IOException, SQLException { // not "throws Exception"
try {
readFile(); // throws IOException
updateDatabase(); // throws SQLException
} catch (Exception e) {
log(e);
throw e; // known to be IOException or SQLException
}
}
try-with-resources
TryWithResourcesStatement:
try ResourceSpecification Block [Catches] [Finally]
ResourceSpecification:
( Resource {; Resource} [;] )
Resource:
LocalVariableDeclaration
VariableAccess
A resource is an object of a type that implements AutoCloseable
(Java 7). The statement closes each resource when the block ends, however it ends:
try (var in = Files.newInputStream(source);
var out = Files.newOutputStream(target)) {
in.transferTo(out);
} // out is closed, then in, even if transferTo throws
BufferedReader reader = Files.newBufferedReader(path);
try (reader) { // Java 9: an effectively final variable as the resource
return reader.readLine();
}
- Resources are initialised left to right and closed in the reverse order. Only
those whose initialisation succeeded are closed. A resource that is
nullis not closed. - Resource variables are implicitly
final. Since Java 9 a resource may also be a reference to an existing final or effectively final variable or field (this.out). - If the block throws and
close()also throws, the exception fromclose()is added to the first one as a suppressed exception (seeThrowable.getSuppressed()) and the first one is thrown. - Catch and finally clauses of a try-with-resources run after the resources are closed.
- The resource may be unnamed (Java 22), when only its closing matters:
try (var _ = openSession()) { … }. - The
close()method’s checked exceptions must be handled or declared, like any others.
At run time
When an exception is thrown, the JVM leaves each statement, expression and method
in turn until it reaches a try statement with a catch clause that
matches, running finally blocks and closing resources on the way
(JLS 11.3). Locks held by synchronized blocks and methods are released.
If no handler is found in the thread, the thread ends and its uncaught exception
handler is called, which by default prints the stack trace; when the last non-daemon
thread ends, the program exits.
Exceptions are precise: the state is as if every expression and statement before the point of the throw had completed and none after it had begun (JLS 11.1.3).