What you'll learn
Quick Answer
Checked exceptions must be caught or declared; unchecked ones need not be. Catch specific types, never swallow an exception silently, and use try-with-resources so resources close even when something fails.
The hierarchy, which explains the rules
Everything throwable descends from Throwable, which splits in two:
- Error — serious problems your code should not catch, such as
OutOfMemoryErrororStackOverflowError. - Exception — conditions your program may reasonably handle.
Under Exception, RuntimeException and its subclasses are unchecked; everything else is checked.
Checked exceptions must be caught or declared with throws, and the compiler enforces it. IOException and SQLException are the ones you will meet constantly.
Unchecked exceptions require nothing. NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException and ArithmeticException are all unchecked, on the reasoning that they represent programming errors rather than conditions to recover from.
Catching, with real messages
try {
int[] a = new int[3];
a[5] = 1;
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("caught: " + e.getMessage());
}
// caught: Index 5 out of bounds for length 3
try {
System.out.println(divide(10, 0));
} catch (ArithmeticException e) {
System.out.println("ArithmeticException: " + e.getMessage());
} finally {
System.out.println("finally always runs");
}
// ArithmeticException: / by zero
// finally always runs
Java's messages are genuinely informative — "Index 5 out of bounds for length 3" tells you the value and the limit. Printing e.getMessage() rather than a generic string of your own preserves that.
Catch specific types, most specific first. A catch (Exception e) placed before a more specific catch is a compile error, which is one of the few places Java protects you from this mistake.
The two habits that cause real damage
The empty catch block.
try {
riskyOperation();
} catch (Exception e) {
// ignored
}
This is the worst thing in this article. The failure happened, nobody knows, and the program continues with wrong state. The eventual bug surfaces somewhere unrelated with no trace of the original cause. Students write these to make the compiler stop complaining about checked exceptions, and it is precisely the wrong response.
If you genuinely cannot handle it, at minimum log it with the exception object included.
e.printStackTrace() as error handling. Acceptable while learning, wrong in anything real — it writes to standard error, is not captured by logging systems, and provides no context. Use a logger and include what was being attempted.
And never catch Exception broadly just to make code compile. That catches NullPointerException from your own bugs too, hiding them.
try-with-resources
Before Java 7, closing a file correctly required a nested finally with its own try/catch — verbose enough that people skipped it, leaking file handles and connections.
try (BufferedReader r = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = r.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
logger.error("could not read data.txt", e);
}
Anything declared in the parentheses is closed automatically when the block exits, whether normally or by exception. Any class implementing AutoCloseable works — readers, streams, database connections and statements.
Use it for every resource. Unclosed database connections are a standard cause of an application that runs fine for an hour and then stops accepting requests, because the connection pool is exhausted.
Custom exceptions and the design question
public class StudentNotFoundException extends RuntimeException {
public StudentNotFoundException(Long id) {
super("Student not found: " + id);
}
}
Extending RuntimeException makes it unchecked; extending Exception makes it checked and forces every caller to handle or declare it.
Most modern Java code favours unchecked custom exceptions. Checked exceptions propagate up through every intermediate method signature, and the usual result is developers wrapping them in empty catch blocks — the exact anti-pattern above. Spring and most current frameworks use unchecked exceptions throughout.
Two rules whichever you choose. Include the useful value in the message — "Student not found: 42" beats "not found". And when wrapping an exception, pass the original as the cause: throw new ServiceException("failed to load", e). Losing the original stack trace makes the real cause unfindable.
The equivalent discussion in Python is in Python exception handling, where all exceptions are unchecked.
