Exception Handling in Java
Handling runtime errors gracefully with try/catch/finally, and the important difference between checked and unchecked exceptions.
2 min read
Things go wrong at runtime — files that don't exist, invalid input, network calls that fail. Java's exception-handling model gives you a structured way to detect and respond to these failures without the whole program crashing.
try, catch, and finally
try {
int result = 10 / 0; // throws ArithmeticException
System.out.println(result); // never reached
} catch (ArithmeticException e) {
System.out.println("Can't divide by zero: " + e.getMessage());
} finally {
System.out.println("This always runs, error or not.");
}Code that might fail goes in the try block. If an exception of a matching type is thrown, control jumps to the matching catch block. finally runs regardless of whether an exception occurred — commonly used to release resources like open files or network connections.
Checked vs. unchecked exceptions
This distinction is one of Java's more unusual design choices, and it matters constantly in practice.
- Checked exceptions (subclasses of
Exception, excludingRuntimeException) must be either caught or declared withthrows— the compiler enforces this.IOExceptionis the classic example: any code that might fail to read a file is forced to acknowledge that possibility. - Unchecked exceptions (subclasses of
RuntimeException, likeNullPointerException,ArrayIndexOutOfBoundsException, andIllegalArgumentException) don't require explicit handling — they typically represent programming mistakes rather than expected, recoverable conditions.
public void readFile(String path) throws IOException {
// any code here that might throw IOException must either be
// caught here, or declared with throws so the caller handles it
}Catching multiple exception types
try {
riskyOperation();
} catch (IOException | InterruptedException e) {
System.out.println("Something went wrong: " + e.getMessage());
}Multiple catch blocks (or, since Java 7, a single multi-catch with |) let you handle different failure types differently, or the same way when that makes sense.
Throwing your own exceptions
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative: " + age);
}
this.age = age;
}Throwing a clear, specific exception with a descriptive message is far more useful to whoever calls your code than letting invalid state silently propagate and fail somewhere unrelated later.
try-with-resources
For anything that needs closing (files, database connections), try-with-resources guarantees close() is called automatically, even if an exception occurs:
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("Couldn't read the file: " + e.getMessage());
}This is the modern, preferred pattern over manually closing resources in a finally block — it's shorter and much harder to get wrong.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.