08 · Exception Handling Basics¶
🎥 Video walkthrough¶
Exceptions represent errors or unusual conditions detected at runtime. Java lets you handle them explicitly instead of crashing the whole program.
try / catch / finally¶
public class Divide {
public static void main(String[] args) {
int a = 10;
int b = 0;
try {
int result = a / b; // throws ArithmeticException
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero: " + e.getMessage());
} finally {
System.out.println("This always runs, error or not.");
}
System.out.println("Program continues normally.");
}
}
// Output:
// Cannot divide by zero: / by zero
// This always runs, error or not.
// Program continues normally.
finally runs whether or not an exception was thrown — commonly used for
cleanup (closing files, releasing resources).
Catching multiple exception types¶
public class ParseInput {
public static void main(String[] args) {
String[] inputs = {"42", "oops", null};
for (String input : inputs) {
try {
int value = Integer.parseInt(input);
System.out.println("Parsed: " + value);
} catch (NumberFormatException e) {
System.out.println("Not a number: " + input);
} catch (NullPointerException e) {
System.out.println("Input was null");
}
}
}
}
// Parsed: 42
// Not a number: oops
// Input was null
You can also catch several types in one clause with |:
try {
riskyOperation();
} catch (NumberFormatException | NullPointerException e) {
System.out.println("Bad input: " + e.getMessage());
}
Checked vs. unchecked exceptions¶
Java has two families of exceptions:
- Checked exceptions (subclasses of
Exceptionbut notRuntimeException) — the compiler forces you to either catch them or declarethrowson the method. Examples:IOException,SQLException. - Unchecked exceptions (subclasses of
RuntimeException) — the compiler does not require handling. Examples:NullPointerException,ArithmeticException,IllegalArgumentException,IndexOutOfBoundsException.
import java.io.FileReader;
import java.io.IOException;
// Checked exception -- must declare "throws" or wrap in try/catch
public static void readFile(String path) throws IOException {
FileReader reader = new FileReader(path); // can throw IOException
reader.close();
}
public static void main(String[] args) {
try {
readFile("nonexistent.txt");
} catch (IOException e) {
System.out.println("File problem: " + e.getMessage());
}
}
| Kind | Extends | Must be declared/caught? | Examples |
|---|---|---|---|
| Checked | Exception |
Yes | IOException, SQLException |
| Unchecked | RuntimeException |
No | NullPointerException, ArithmeticException |
| Error | Error |
No (don't catch these) | OutOfMemoryError, StackOverflowError |
Throwing your own exceptions¶
public class AgeValidator {
static void validate(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Age out of range: " + age);
}
}
public static void main(String[] args) {
try {
validate(-5);
} catch (IllegalArgumentException e) {
System.out.println("Validation failed: " + e.getMessage());
}
}
}
Custom exception classes (extending Exception or RuntimeException) are
covered in Level 2, along with
try-with-resources for automatic cleanup.
How It Actually Works¶
A try/catch block doesn't cost anything when no exception is thrown:
the compiler emits an exception table attached to the method — a list
of (start_pc, end_pc, handler_pc, catch_type) ranges — rather than any
runtime check. Throwing an exception is what's expensive: throw
triggers Throwable's constructor to call fillInStackTrace(), a native
method that walks every frame on the current thread's call stack and
snapshots it, which is real, non-trivial work (this is why throwing
exceptions for ordinary control flow is a measurable performance
mistake, not just a style complaint).
When an exception propagates, the JVM doesn't unwind gently — the
interpreter/JIT-compiled code checks the current method's exception table
for a matching handler_pc; if none matches, the frame is popped
entirely and the search continues in the caller's exception table, all
the way up until a handler is found or the thread dies with an uncaught
exception handler print.
Checked vs. unchecked is a compile-time-only distinction — nothing
in the bytecode or the JVM itself treats IOException differently from
RuntimeException; javac alone enforces that checked exceptions
appear in a throws clause or get caught, which is why reflection can
freely throw a checked exception from a method that doesn't declare it.
🔀 See this in another language¶
Exercise¶
Write a method safeDivide(int a, int b) that returns the division result, but
catches ArithmeticException internally and returns 0 instead of crashing
when dividing by zero (print a warning message when this happens). Then write
a method parseAge(String input) that parses a string to an int using
Integer.parseInt, catches NumberFormatException, and throws a new
IllegalArgumentException with a clearer message ("Invalid age: " + input) if
parsing fails.