Core Java · Lesson 9
Exception Handling
Understanding Exceptions and Error Handling
An exception is an event that disrupts normal program flow — like trying to divide by zero, accessing a null object, or opening a missing file. Exception handling lets you detect these problems and respond gracefully instead of crashing. Java's exception system separates error handling code from business logic, making programs more robust and readable. Proper exception handling is essential for production code.
Try-Catch-Finally: The Basics
Use a try block to wrap code that might throw an exception. A catch block handles the exception if one occurs. A finally block (optional) runs whether an exception occurred or not — use it for cleanup. This structure lets you separate risky code from error handling.
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ExceptionHandling {
public static void main(String[] args) {
try {
// Risky code that might throw an exception
File file = new File("nonexistent.txt");
Scanner scanner = new Scanner(file);
String line = scanner.nextLine();
System.out.println(line);
} catch (FileNotFoundException e) {
// Handle this specific exception
System.out.println("File not found: " + e.getMessage());
} finally {
// Runs regardless: cleanup code
System.out.println("Attempt to read file completed");
}
}
}
Checked vs Unchecked Exceptions
Checked exceptions (subclasses of Exception) must be caught or declared with throws; the compiler enforces this. Common checked exceptions: IOException, SQLException, FileNotFoundException. Unchecked exceptions (subclasses of RuntimeException) occur at runtime and don't require explicit handling. Common unchecked exceptions: NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException.
public class ExceptionTypes {
// Method that throws a checked exception
public void readFile(String filename) throws FileNotFoundException {
File file = new File(filename);
Scanner scanner = new Scanner(file); // throws FileNotFoundException
}
// Caller must handle checked exception
public void callReadFile() {
try {
readFile("data.txt");
} catch (FileNotFoundException e) {
System.out.println("Error: " + e.getMessage());
}
}
// Unchecked exception example
public int divide(int a, int b) {
return a / b; // throws ArithmeticException if b is 0
}
public static void main(String[] args) {
ExceptionTypes handler = new ExceptionTypes();
// Checked: must handle
handler.callReadFile();
// Unchecked: handling is optional (but recommended)
try {
int result = handler.divide(10, 0);
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero");
}
}
}
Multiple Catch Blocks and Exception Hierarchy
You can catch multiple exceptions in separate catch blocks. Java tries them in order, so place more specific exceptions before general ones. Java 7+ allows catching multiple exceptions in a single catch with the pipe operator |.
public class MultiCatchExample {
public void processUserInput(String[] data) {
try {
// Multiple risky operations
String name = data[0]; // ArrayIndexOutOfBoundsException
int age = Integer.parseInt(name); // NumberFormatException
System.out.println("Name: " + name + ", Age: " + age);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Missing data: " + e.getMessage());
} catch (NumberFormatException e) {
System.out.println("Invalid number format: " + e.getMessage());
} catch (Exception e) {
// General fallback: must be last
System.out.println("Unexpected error: " + e.getMessage());
}
}
// Java 7+: catch multiple exceptions at once
public void processInput(String[] data) {
try {
String name = data[0];
int age = Integer.parseInt(name);
} catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
Throwing Exceptions: Creating Your Own Errors
Use the throw keyword to explicitly throw an exception. This is useful when you detect invalid conditions and want to alert callers. You can throw built-in exceptions or create custom exceptions by extending Exception or RuntimeException.
public class UserValidator {
public void validateAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Age must be between 0 and 150");
}
System.out.println("Age valid: " + age);
}
public void validateEmail(String email) throws InvalidEmailException {
if (!email.contains("@")) {
throw new InvalidEmailException("Email must contain @");
}
System.out.println("Email valid: " + email);
}
}
// Custom exception
public class InvalidEmailException extends Exception {
public InvalidEmailException(String message) {
super(message);
}
}
public class ValidationDemo {
public static void main(String[] args) {
UserValidator validator = new UserValidator();
try {
validator.validateAge(-5);
} catch (IllegalArgumentException e) {
System.out.println("Caught: " + e.getMessage());
}
try {
validator.validateEmail("invalid-email");
} catch (InvalidEmailException e) {
System.out.println("Caught: " + e.getMessage());
}
}
}
Try-With-Resources: Automatic Resource Management
The try-with-resources statement (Java 7+) automatically closes resources (files, connections, streams) even if an exception occurs. Any resource implementing AutoCloseable can be used. This eliminates the need for explicit finally blocks just for cleanup.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class TryWithResourcesExample {
public void readFile(String filename) {
// try-with-resources: automatically closes BufferedReader
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("Error reading file: " + e.getMessage());
}
// reader is automatically closed here
}
public static void main(String[] args) {
TryWithResourcesExample app = new TryWithResourcesExample();
app.readFile("data.txt");
}
}
| Exception Type | Checked? | When it Occurs | Examples |
|---|---|---|---|
| Checked | Yes, must handle | Known, predictable problems | IOException, SQLException, FileNotFoundException |
| Unchecked (Runtime) | No, optional | Logic errors, unexpected conditions | NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException |
| Error | No | Serious JVM problems | OutOfMemoryError, StackOverflowError |
Common Mistake: Catching
Exceptiontoo broadly.catch (Exception e) { }silently swallows all errors, making debugging impossible. Catch specific exceptions instead. If you need a general fallback, log it and re-throw:catch (Exception e) { log.error(e); throw e; }. Also, avoid empty catch blocks — always log or handle somehow.
Best Practices for Exception Handling
- Catch specific exceptions, not generic
Exception - Provide meaningful error messages: help yourself and others debug
- Use try-with-resources for files, connections, streams (Java 7+)
- Don't ignore exceptions: always log or handle them
- Use unchecked exceptions for programming errors (conditions callers can't fix)
- Use checked exceptions for recoverable conditions (callers can retry)
- Log at the point where you handle, not at every level
- Clean up in
finallyblocks or try-with-resources, not in catch blocks
Real-World Example: Database Operation Handler
A banking system wraps database operations in try-catch-finally: the try block executes a transaction, catch blocks handle SQLException (database down), DataNotFoundException (account not found), and log each differently. The finally block closes the database connection. When an error occurs, the system logs it with full context (account ID, operation type), notifies the user with a user-friendly message, and maintains data integrity. By properly handling exceptions, the bank's systems stay reliable and trustworthy even when things go wrong.
Quiz Check