Java Exception Handling
Complete guide to Java exception handling with examples. Learn try-catch-finally, throw, throws, custom exceptions, and exception hierarchy.
Exception handling is a powerful mechanism in Java that allows you to handle runtime errors gracefully, ensuring that your program doesn't crash unexpectedly. Instead of terminating abruptly, your program can recover from errors and continue execution.
What is an Exception?
An exception is an unexpected event that occurs during program execution and disrupts the normal flow of the program. Examples include:
- Dividing by zero
- Accessing an array element out of bounds
- Opening a file that doesn't exist
- Network connection failures
Exception Hierarchy in Java
Object
|
Throwable
/ \
Error Exception
/ \
RuntimeException Checked Exceptions
(Unchecked) (IOException, etc.)
Types of Exceptions
- Checked Exceptions: Must be handled at compile time (e.g., IOException, SQLException)
- Unchecked Exceptions: Runtime exceptions that can be handled optionally (e.g., NullPointerException, ArrayIndexOutOfBoundsException)
- Errors: Serious problems that applications shouldn't try to handle (e.g., OutOfMemoryError)
Basic Exception Handling with try-catch
The try-catch
block is used to handle exceptions in Java.
Syntax
try {
// Code that might throw an exception
} catch (ExceptionType e) {
// Code to handle the exception
}
Example: Handling ArithmeticException
class Main {
public static void main(String[] args) {
try {
int a = 10;
int b = 0;
int result = a / b; // This will throw ArithmeticException
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Error: Cannot divide by zero!");
System.out.println("Exception message: " + e.getMessage());
}
System.out.println("Program continues...");
}
}
Output:
Error: Cannot divide by zero!
Exception message: / by zero
Program continues...
Multiple catch Blocks
You can handle different types of exceptions using multiple catch blocks.
class Main {
public static void main(String[] args) {
try {
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // ArrayIndexOutOfBoundsException
int result = 10 / 0; // ArithmeticException
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array index out of bounds: " + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("Arithmetic error: " + e.getMessage());
} catch (Exception e) {
System.out.println("General exception: " + e.getMessage());
}
}
}
Note: More specific exception types should be caught before general ones. The Exception
class should be the last catch block as it's the parent of all exceptions.
The finally Block
The finally
block contains code that executes regardless of whether an exception occurs or not. It's typically used for cleanup operations.
class Main {
public static void main(String[] args) {
try {
int result = 10 / 2;
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Exception caught: " + e.getMessage());
} finally {
System.out.println("This always executes!");
}
System.out.println("Program ends");
}
}
Output:
Result: 5
This always executes!
Program ends
Example: File Handling with finally
import java.io.*;
class FileExample {
public static void main(String[] args) {
FileReader file = null;
try {
file = new FileReader("test.txt");
// Read file operations
System.out.println("File opened successfully");
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
} finally {
// Cleanup: Close the file
if (file != null) {
try {
file.close();
System.out.println("File closed");
} catch (IOException e) {
System.out.println("Error closing file: " + e.getMessage());
}
}
}
}
}
The throw Keyword
The throw
keyword is used to explicitly throw an exception.
class AgeValidator {
static void validateAge(int age) {
if (age < 18) {
throw new IllegalArgumentException("Age must be 18 or older");
}
System.out.println("Age is valid: " + age);
}
public static void main(String[] args) {
try {
validateAge(16);
} catch (IllegalArgumentException e) {
System.out.println("Exception: " + e.getMessage());
}
validateAge(25); // This will execute normally
}
}
Output:
Exception: Age must be 18 or older
Age is valid: 25
The throws Keyword
The throws
keyword is used in method signatures to declare that the method might throw certain exceptions.
import java.io.*;
class FileHandler {
// Method declares that it might throw IOException
static void readFile(String fileName) throws IOException {
FileReader file = new FileReader(fileName);
BufferedReader reader = new BufferedReader(file);
System.out.println(reader.readLine());
reader.close();
}
public static void main(String[] args) {
try {
readFile("example.txt");
} catch (IOException e) {
System.out.println("File error: " + e.getMessage());
}
}
}
Custom Exceptions
You can create your own exception classes by extending the Exception
class or its subclasses.
// Custom exception class
class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
class BankAccount {
private double balance;
public BankAccount(double balance) {
this.balance = balance;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(
"Insufficient funds. Available: $" + balance + ", Requested: $" + amount
);
}
balance -= amount;
System.out.println("Withdrawal successful. New balance: $" + balance);
}
public double getBalance() {
return balance;
}
}
class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount(1000.0);
try {
account.withdraw(500.0); // Success
account.withdraw(600.0); // This will throw custom exception
} catch (InsufficientFundsException e) {
System.out.println("Transaction failed: " + e.getMessage());
}
}
}
Output:
Withdrawal successful. New balance: $500.0
Transaction failed: Insufficient funds. Available: $500.0, Requested: $600.0
Common Exception Types
1. NullPointerException
Thrown when trying to access methods or fields of a null reference.
class Main {
public static void main(String[] args) {
try {
String str = null;
int length = str.length(); // NullPointerException
} catch (NullPointerException e) {
System.out.println("Null pointer error: " + e.getMessage());
}
}
}
2. ArrayIndexOutOfBoundsException
Thrown when accessing an array with an invalid index.
class Main {
public static void main(String[] args) {
try {
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // Invalid index
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array index error: " + e.getMessage());
}
}
}
3. NumberFormatException
Thrown when trying to convert an invalid string to a number.
class Main {
public static void main(String[] args) {
try {
String str = "abc";
int number = Integer.parseInt(str); // Invalid number format
} catch (NumberFormatException e) {
System.out.println("Number format error: " + e.getMessage());
}
}
}
Exception Handling Best Practices
1. Use Specific Exception Types
// Good
try {
// code
} catch (FileNotFoundException e) {
// handle file not found
} catch (IOException e) {
// handle other IO errors
}
// Avoid
try {
// code
} catch (Exception e) {
// too generic
}
2. Don't Ignore Exceptions
// Bad
try {
// risky code
} catch (Exception e) {
// empty catch block - never do this!
}
// Good
try {
// risky code
} catch (Exception e) {
logger.error("Error occurred: " + e.getMessage(), e);
// or handle appropriately
}
3. Use try-with-resources for Resource Management
// Automatic resource management (Java 7+)
try (FileReader file = new FileReader("example.txt");
BufferedReader reader = new BufferedReader(file)) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("File error: " + e.getMessage());
}
// File is automatically closed
Real-World Example: User Input Validation
import java.util.Scanner;
class UserInputValidator {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (true) {
try {
System.out.print("Enter your age: ");
String input = scanner.nextLine();
int age = Integer.parseInt(input);
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
if (age > 150) {
throw new IllegalArgumentException("Age seems unrealistic");
}
System.out.println("Valid age: " + age);
break; // Exit loop on successful input
} catch (NumberFormatException e) {
System.out.println("Please enter a valid number!");
} catch (IllegalArgumentException e) {
System.out.println("Invalid age: " + e.getMessage());
}
}
scanner.close();
}
}
Exception Propagation
When an exception is not handled in a method, it propagates up the call stack.
class ExceptionPropagation {
static void method1() {
int result = 10 / 0; // ArithmeticException thrown here
}
static void method2() {
method1(); // Exception propagates from method1
}
static void method3() {
try {
method2(); // Exception propagates from method2
} catch (ArithmeticException e) {
System.out.println("Exception caught in method3: " + e.getMessage());
}
}
public static void main(String[] args) {
method3();
System.out.println("Program continues...");
}
}
Key Points to Remember
- Use
try-catch
blocks to handle exceptions gracefully - Always handle specific exceptions before general ones
- Use
finally
block for cleanup operations - Use
throw
to explicitly throw exceptions - Use
throws
in method signatures to declare possible exceptions - Create custom exceptions by extending
Exception
class - Never ignore exceptions with empty catch blocks
- Use try-with-resources for automatic resource management
- Exceptions propagate up the call stack if not handled
Best Practice: Handle exceptions at the appropriate level in your application. Don't catch exceptions too early if you can't meaningfully handle them at that point.