Checked & Unchecked Exceptions
Exception handling is one of Java's most important reliability features. It provides a structured mechanism for detecting, reporting, and recovering from errors at runtime.
The Exception Hierarchy
Java's exception system is built on a class hierarchy rooted at Throwable:
ThrowableError— serious JVM problems (OutOfMemoryError, StackOverflowError). Do not catch these.Exception— recoverable conditions that programs should handle.RuntimeException— unchecked exceptions (programming mistakes).
Checked vs Unchecked Exceptions
Checked exceptions extend Exception (but not RuntimeException). The compiler forces you to either handle them with try/catch or declare them with throws in the method signature. Examples: IOException, SQLException, ParseException.
Unchecked exceptions extend RuntimeException. The compiler does not require you to handle them, though you can. Examples: NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException, NumberFormatException.
try / catch / finally
The try block contains code that may throw. One or more catch blocks handle specific exception types — more specific types must appear before more general ones. The optional finally block always executes whether an exception occurred or not, making it ideal for releasing resources.
Multi-catch (Java 7+)
When two exceptions require identical handling you can combine them in a single catch clause with the pipe operator: catch (IOException | SQLException e). The catch parameter is implicitly final in multi-catch.
throw vs throws
throw(lowercase) is a statement that actually throws an exception object:throw new IllegalArgumentException("msg");throws(with s) is part of a method declaration that advertises which checked exceptions callers must handle.
Reading Stack Traces
When an exception propagates uncaught, Java prints a stack trace showing the exception type, message, and the call chain from the throw site down to main. The topmost frame is closest to the source of the problem. Reading stack traces is an essential debugging skill.
Code Examples
public class TryCatchDemo {
public static int divide(int a, int b) {
return a / b; // throws ArithmeticException if b == 0
}
public static void main(String[] args) {
// Basic try-catch
try {
int result = divide(10, 2);
System.out.println("10 / 2 = " + result);
int bad = divide(5, 0); // throws ArithmeticException
System.out.println("This line never runs");
} catch (ArithmeticException e) {
System.out.println("Caught: " + e.getMessage());
} finally {
System.out.println("finally always runs");
}
// Catching multiple exception types separately
String[] names = {"Alice", "Bob"};
try {
System.out.println(names[5]); // ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Index error: " + e.getMessage());
} catch (Exception e) {
// General catch — must come AFTER specific ones
System.out.println("General error: " + e.getMessage());
}
// NumberFormatException (unchecked)
try {
int n = Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println("Parse error: " + e.getMessage());
}
}
}ArithmeticException is unchecked (extends RuntimeException) so no throws declaration is needed. The finally block always executes — even when an exception is caught.
public class MultiCatchDemo {
// throws declares a checked exception callers must handle
public static String readConfig(String key) throws java.io.IOException {
if (key == null) {
throw new IllegalArgumentException("key must not be null");
}
if (key.isEmpty()) {
throw new java.io.IOException("Cannot read config: empty key");
}
return "value-" + key;
}
public static void parseAndPrint(String input) {
try {
// Two different unchecked exceptions — handled identically
int value = Integer.parseInt(input); // NumberFormatException
int[] arr = new int[value];
System.out.println(arr[value + 1]); // ArrayIndexOutOfBoundsException
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
// Multi-catch: e is implicitly final here
System.out.println("Caught (" + e.getClass().getSimpleName() + "): " + e.getMessage());
}
}
public static void main(String[] args) {
// Must handle checked IOException
try {
System.out.println(readConfig("host"));
System.out.println(readConfig("")); // triggers IOException
} catch (java.io.IOException e) {
System.out.println("IO problem: " + e.getMessage());
}
// Unchecked — no try required, but we demonstrate multi-catch
parseAndPrint("abc"); // NumberFormatException path
parseAndPrint("3"); // ArrayIndexOutOfBoundsException path
}
}Multi-catch (A | B e) avoids duplicating catch bodies. The throws keyword on readConfig forces every caller to either catch IOException or propagate it further.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class CheckedDemo {
// Declares checked IOException — callers must deal with it
public static String loadFile(Path path) throws IOException {
if (!Files.exists(path)) {
throw new IOException("File not found: " + path);
}
return Files.readString(path);
}
// Wraps loadFile and handles the checked exception locally
public static String safeLoad(Path path) {
try {
return loadFile(path);
} catch (IOException e) {
// Log and return a safe default instead of crashing
System.err.println("Warning: " + e.getMessage());
return "";
}
}
public static void main(String[] args) {
Path missing = Path.of("nonexistent.txt");
// Option 1: handle at call site
try {
String content = loadFile(missing);
System.out.println(content);
} catch (IOException e) {
System.out.println("Handled at main: " + e.getMessage());
}
// Option 2: use the wrapper that handles internally
String result = safeLoad(missing);
System.out.println("Safe result: '" + result + "'");
// Stack trace details
try {
throw new IOException("demo error");
} catch (IOException e) {
System.out.println("Class : " + e.getClass().getName());
System.out.println("Msg : " + e.getMessage());
// e.printStackTrace() would print the full stack trace
}
}
}Checked exceptions force callers to acknowledge failure paths at compile time. You can either handle them (try/catch) or propagate them (add throws to your own signature).
Quick Quiz
1. Which of these is a checked exception?
2. When does the finally block execute?
3. What is the correct order for multiple catch blocks?
4. What does the `throws` keyword on a method signature mean?
Was this lesson helpful?