Exception handling is a crucial mechanism in Java that allows programs to handle runtime errors gracefully, maintaining normal program flow.
1. What are Exceptions?
Exceptions are events that disrupt the normal flow of program execution. Java provides a robust framework for handling these exceptions.
Exception Hierarchy
Throwable │ ├── Error (unchecked) │ ├── OutOfMemoryError │ ├── StackOverflowError │ └── VirtualMachineError │ └── Exception ├── RuntimeException (unchecked) │ ├── NullPointerException │ ├── ArrayIndexOutOfBoundsException │ ├── IllegalArgumentException │ └── ArithmeticException │ └── Checked Exceptions ├── IOException ├── SQLException ├── FileNotFoundException └── ClassNotFoundException
2. Types of Exceptions
Checked Exceptions
- Checked at compile-time
- Must be handled using try-catch or declared with throws
Unchecked Exceptions
- Checked at runtime
- Subclasses of RuntimeException
Errors
- Serious problems that applications should not try to catch
- e.g., OutOfMemoryError, StackOverflowError
3. Basic Exception Handling Syntax
try-catch Block
public class BasicExceptionHandling {
public static void main(String[] args) {
try {
// Code that might throw an exception
int result = divide(10, 0);
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
// Handle the exception
System.out.println("Error: Cannot divide by zero!");
System.out.println("Exception message: " + e.getMessage());
}
System.out.println("Program continues normally...");
}
public static int divide(int a, int b) {
return a / b;
}
}
Multiple catch Blocks
public class MultipleCatchBlocks {
public static void main(String[] args) {
try {
int[] numbers = {1, 2, 3};
// This will throw ArrayIndexOutOfBoundsException
System.out.println(numbers[5]);
// This would throw ArithmeticException (won't reach here)
int result = 10 / 0;
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array index error: " + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("Arithmetic error: " + e.getMessage());
} catch (Exception e) {
System.out.println("General error: " + e.getMessage());
}
}
}
Multi-catch Block (Java 7+)
public class MultiCatchExample {
public static void main(String[] args) {
try {
String str = null;
System.out.println(str.length()); // NullPointerException
int[] arr = new int[5];
arr[10] = 100; // ArrayIndexOutOfBoundsException
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
System.out.println("Caught exception: " + e.getClass().getSimpleName());
System.out.println("Message: " + e.getMessage());
}
}
}
4. finally Block
The finally block always executes, regardless of whether an exception occurred or not.
public class FinallyExample {
public static void main(String[] args) {
FileInputStream file = null;
try {
file = new FileInputStream("test.txt");
// Read file content
System.out.println("File opened successfully");
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
} finally {
// This block always executes
System.out.println("Finally block executed");
if (file != null) {
try {
file.close();
System.out.println("File closed");
} catch (IOException e) {
System.out.println("Error closing file: " + e.getMessage());
}
}
}
}
}
5. try-with-resources (Java 7+)
Automatically closes resources that implement AutoCloseable interface.
public class TryWithResourcesExample {
public static void main(String[] args) {
// Resources are automatically closed
try (FileInputStream input = new FileInputStream("test.txt");
FileOutputStream output = new FileOutputStream("output.txt")) {
// Work with resources
int data;
while ((data = input.read()) != -1) {
output.write(data);
}
System.out.println("File copied successfully");
} catch (IOException e) {
System.out.println("IO Error: " + e.getMessage());
}
// No need for finally block - resources are automatically closed
}
}
6. Throwing Exceptions
throw keyword
public class ThrowExample {
public static void validateAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age + ". Age must be between 0 and 150.");
}
System.out.println("Valid age: " + age);
}
public static void main(String[] args) {
try {
validateAge(25); // Valid
validateAge(-5); // Throws exception
validateAge(200); // Won't reach here
} catch (IllegalArgumentException e) {
System.out.println("Caught exception: " + e.getMessage());
}
}
}
throws keyword
public class ThrowsExample {
// Method declares that it might throw IOException
public static void readFile(String filename) throws IOException {
if (filename == null || filename.isEmpty()) {
throw new IOException("Filename cannot be null or empty");
}
File file = new File(filename);
if (!file.exists()) {
throw new FileNotFoundException("File not found: " + filename);
}
// Read file content
System.out.println("Reading file: " + filename);
}
public static void main(String[] args) {
try {
readFile("data.txt");
readFile(""); // This will throw IOException
} catch (IOException e) {
System.out.println("IO Exception: " + e.getMessage());
}
}
}
7. Custom Exceptions
Creating Custom Checked Exception
// Custom checked exception
class InsufficientFundsException extends Exception {
private double amount;
private double balance;
public InsufficientFundsException(double amount, double balance) {
super("Insufficient funds: Attempted to withdraw $" + amount +
" but balance is $" + balance);
this.amount = amount;
this.balance = balance;
}
public double getAmount() { return amount; }
public double getBalance() { return balance; }
}
Creating Custom Unchecked Exception
// Custom unchecked exception
class InvalidAccountException extends RuntimeException {
private String accountNumber;
public InvalidAccountException(String accountNumber) {
super("Invalid account number: " + accountNumber);
this.accountNumber = accountNumber;
}
public String getAccountNumber() { return accountNumber; }
}
Using Custom Exceptions
public class BankAccount {
private String accountNumber;
private double balance;
public BankAccount(String accountNumber, double initialBalance) {
if (accountNumber == null || accountNumber.length() != 8) {
throw new InvalidAccountException(accountNumber);
}
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(amount, balance);
}
balance -= amount;
System.out.println("Withdrawn: $" + amount + ", New balance: $" + balance);
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit amount must be positive");
}
balance += amount;
System.out.println("Deposited: $" + amount + ", New balance: $" + balance);
}
// Getters
public double getBalance() { return balance; }
public String getAccountNumber() { return accountNumber; }
}
Testing Custom Exceptions
public class BankApp {
public static void main(String[] args) {
try {
BankAccount account = new BankAccount("12345678", 1000);
account.deposit(500);
account.withdraw(200);
account.withdraw(2000); // This will throw InsufficientFundsException
} catch (InsufficientFundsException e) {
System.out.println("Bank error: " + e.getMessage());
System.out.println("Attempted: $" + e.getAmount() + ", Available: $" + e.getBalance());
} catch (InvalidAccountException e) {
System.out.println("Account error: " + e.getMessage());
}
}
}
8. Exception Propagation
public class ExceptionPropagation {
public static void methodA() {
System.out.println("In method A");
methodB();
System.out.println("Back to method A"); // Won't execute if exception occurs
}
public static void methodB() {
System.out.println("In method B");
methodC();
System.out.println("Back to method B"); // Won't execute if exception occurs
}
public static void methodC() {
System.out.println("In method C");
// This exception will propagate up the call stack
throw new RuntimeException("Exception from method C");
}
public static void main(String[] args) {
try {
methodA();
} catch (RuntimeException e) {
System.out.println("Caught in main: " + e.getMessage());
System.out.println("Stack trace:");
e.printStackTrace();
}
}
}
9. Best Practices
1. Be Specific in Catch Blocks
// Good
try {
// code
} catch (FileNotFoundException e) {
// handle file not found
} catch (IOException e) {
// handle other IO issues
}
// Avoid - too broad
try {
// code
} catch (Exception e) {
// handles everything
}
2. Don't Ignore Exceptions
// Bad - ignoring exception
try {
riskyOperation();
} catch (Exception e) {
// empty catch block - DON'T DO THIS!
}
// Good - at least log it
try {
riskyOperation();
} catch (Exception e) {
System.out.println("Operation failed: " + e.getMessage());
e.printStackTrace();
}
3. Use Finally for Cleanup
public void processFile(String filename) {
FileInputStream file = null;
try {
file = new FileInputStream(filename);
// process file
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
} finally {
if (file != null) {
try {
file.close();
} catch (IOException e) {
System.out.println("Error closing file: " + e.getMessage());
}
}
}
}
4. Prefer Try-with-resources
// Better approach with try-with-resources
public void processFile(String filename) {
try (FileInputStream file = new FileInputStream(filename)) {
// process file
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
// File automatically closed
}
10. Common Exception Handling Patterns
Retry Pattern
public class RetryPattern {
public static void executeWithRetry(Runnable operation, int maxRetries) {
int attempts = 0;
while (attempts <= maxRetries) {
try {
operation.run();
return; // Success - exit method
} catch (Exception e) {
attempts++;
if (attempts > maxRetries) {
throw new RuntimeException("Operation failed after " + maxRetries + " attempts", e);
}
System.out.println("Attempt " + attempts + " failed, retrying...");
// Wait before retry
try {
Thread.sleep(1000 * attempts);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Operation interrupted", ie);
}
}
}
}
public static void main(String[] args) {
executeWithRetry(() -> {
// Simulate unreliable operation
if (Math.random() < 0.7) {
throw new RuntimeException("Temporary failure");
}
System.out.println("Operation successful!");
}, 3);
}
}
Summary
- try-catch: Handle exceptions locally
- finally: Cleanup code that always executes
- throw: Manually throw exceptions
- throws: Declare exceptions a method might throw
- Custom Exceptions: Create application-specific exceptions
- Try-with-resources: Automatic resource management
- Exception Propagation: Exceptions bubble up the call stack
Proper exception handling makes your code more robust, maintainable, and user-friendly by gracefully handling error conditions.