- Introduction to Exceptions
- Exception Hierarchy
- Checked Exceptions
- Unchecked Exceptions
- User-Defined Exceptions
- Comparison Table
- Exception Handling Best Practices
Exceptions in Java are events that disrupt the normal flow of program execution. They represent error conditions that occur during runtime. Java provides a robust exception handling mechanism to handle these exceptional situations gracefully.
All exception classes in Java are derived from the Throwable class. The hierarchy looks like this:
Throwable
├── Error (unchecked)
└── Exception
├── RuntimeException (unchecked)
└── Other Exceptions (checked)
Checked exceptions are exceptions that are checked at compile-time. The compiler forces you to either handle these exceptions using a try-catch block or declare them using the throws keyword. These exceptions represent conditions that a well-written application should anticipate and recover from.
Key Characteristics:
- Must be handled or declared
- Checked at compile-time
- Extend
Exceptionclass (but notRuntimeException) - Represent recoverable conditions
// IOException
import java.io.*;
public class FileExample {
public void readFile(String filename) throws IOException {
FileReader file = new FileReader(filename);
BufferedReader reader = new BufferedReader(file);
String line = reader.readLine();
reader.close();
}
}
// SQLException
import java.sql.*;
public class DatabaseExample {
public void queryDatabase() throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/db");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
}
}
// ClassNotFoundException
public class ClassExample {
public void loadClass() throws ClassNotFoundException {
Class.forName("com.example.MyClass");
}
}Option 1: Try-Catch Block
public class CheckedExceptionHandling {
public void readFile(String filename) {
try {
FileReader file = new FileReader(filename);
BufferedReader reader = new BufferedReader(file);
String line = reader.readLine();
System.out.println(line);
reader.close();
} catch (FileNotFoundException e) {
System.err.println("File not found: " + e.getMessage());
} catch (IOException e) {
System.err.println("Error reading file: " + e.getMessage());
}
}
}Option 2: Throws Declaration
public class CheckedExceptionThrows {
public void readFile(String filename) throws IOException {
FileReader file = new FileReader(filename);
BufferedReader reader = new BufferedReader(file);
String line = reader.readLine();
reader.close();
}
public static void main(String[] args) {
CheckedExceptionThrows example = new CheckedExceptionThrows();
try {
example.readFile("data.txt");
} catch (IOException e) {
e.printStackTrace();
}
}
}Unchecked exceptions are exceptions that are not checked at compile-time. These are also called runtime exceptions. They extend RuntimeException class and represent programming errors that could have been avoided.
Key Characteristics:
- Not required to be handled or declared
- Occur at runtime
- Extend
RuntimeExceptionclass - Represent programming bugs
// NullPointerException
public class NullPointerExample {
public void demonstrateNPE() {
String str = null;
System.out.println(str.length()); // Throws NullPointerException
}
}
// ArrayIndexOutOfBoundsException
public class ArrayIndexExample {
public void demonstrateArrayIndex() {
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // Throws ArrayIndexOutOfBoundsException
}
}
// ArithmeticException
public class ArithmeticExample {
public void demonstrateArithmetic() {
int result = 10 / 0; // Throws ArithmeticException
}
}
// IllegalArgumentException
public class IllegalArgumentExample {
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
}
// NumberFormatException
public class NumberFormatExample {
public void parseNumber() {
int num = Integer.parseInt("abc"); // Throws NumberFormatException
}
}Unchecked exceptions should be used for:
- Programming errors that should be fixed in code
- Validation failures
- Invalid arguments
- Precondition violations
public class UncheckedExceptionExample {
public double calculateSquareRoot(double number) {
if (number < 0) {
throw new IllegalArgumentException("Cannot calculate square root of negative number");
}
return Math.sqrt(number);
}
public void processArray(int[] arr, int index) {
if (arr == null) {
throw new NullPointerException("Array cannot be null");
}
if (index < 0 || index >= arr.length) {
throw new IndexOutOfBoundsException("Index out of valid range");
}
// Process array
}
}User-defined exceptions allow you to create application-specific exception types that provide more meaningful error information.
Custom Checked Exception:
// Extends Exception (checked)
public class InsufficientFundsException extends Exception {
private double amount;
public InsufficientFundsException(double amount) {
super("Insufficient funds. Required: $" + amount);
this.amount = amount;
}
public InsufficientFundsException(String message, double amount) {
super(message);
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
// Usage
public class BankAccount {
private double balance;
public BankAccount(double balance) {
this.balance = balance;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
double shortfall = amount - balance;
throw new InsufficientFundsException(shortfall);
}
balance -= amount;
System.out.println("Withdrawal successful. Remaining balance: $" + balance);
}
public static void main(String[] args) {
BankAccount account = new BankAccount(500.0);
try {
account.withdraw(600.0);
} catch (InsufficientFundsException e) {
System.err.println("Transaction failed: " + e.getMessage());
System.err.println("Shortfall: $" + e.getAmount());
}
}
}Custom Unchecked Exception:
// Extends RuntimeException (unchecked)
public class InvalidAgeException extends RuntimeException {
private int age;
public InvalidAgeException(int age) {
super("Invalid age: " + age);
this.age = age;
}
public InvalidAgeException(String message, int age) {
super(message);
this.age = age;
}
public int getAge() {
return age;
}
}
// Usage
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
setAge(age);
}
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new InvalidAgeException("Age must be between 0 and 150", age);
}
this.age = age;
}
public static void main(String[] args) {
try {
Person person = new Person("John", -5);
} catch (InvalidAgeException e) {
System.err.println("Error: " + e.getMessage());
System.err.println("Invalid age provided: " + e.getAge());
}
}
}When creating user-defined exceptions:
- Choose the right parent class: Extend
Exceptionfor checked exceptions,RuntimeExceptionfor unchecked - Provide constructors: Include default constructor and one with a message parameter
- Add relevant fields: Store additional context information
- Use meaningful names: Exception names should end with "Exception"
- Include detailed messages: Help developers understand what went wrong
public class DataValidationException extends Exception {
private String fieldName;
private Object invalidValue;
// Default constructor
public DataValidationException() {
super();
}
// Constructor with message
public DataValidationException(String message) {
super(message);
}
// Constructor with message and cause
public DataValidationException(String message, Throwable cause) {
super(message, cause);
}
// Constructor with all details
public DataValidationException(String message, String fieldName, Object invalidValue) {
super(message);
this.fieldName = fieldName;
this.invalidValue = invalidValue;
}
public String getFieldName() {
return fieldName;
}
public Object getInvalidValue() {
return invalidValue;
}
}E-commerce Order Processing:
public class OrderProcessingException extends Exception {
private String orderId;
private String errorCode;
public OrderProcessingException(String message, String orderId, String errorCode) {
super(message);
this.orderId = orderId;
this.errorCode = errorCode;
}
public String getOrderId() {
return orderId;
}
public String getErrorCode() {
return errorCode;
}
}
public class OrderService {
public void processOrder(String orderId) throws OrderProcessingException {
// Validation logic
if (orderId == null || orderId.isEmpty()) {
throw new OrderProcessingException(
"Order ID cannot be empty",
orderId,
"ORD_001"
);
}
// Payment processing
if (!processPayment(orderId)) {
throw new OrderProcessingException(
"Payment processing failed",
orderId,
"PAY_001"
);
}
}
private boolean processPayment(String orderId) {
// Payment logic
return false;
}
}Configuration Exception:
public class ConfigurationException extends RuntimeException {
private String configKey;
public ConfigurationException(String message, String configKey) {
super(message);
this.configKey = configKey;
}
public ConfigurationException(String message, String configKey, Throwable cause) {
super(message, cause);
this.configKey = configKey;
}
public String getConfigKey() {
return configKey;
}
}
public class ApplicationConfig {
public String getRequiredProperty(String key) {
String value = System.getProperty(key);
if (value == null) {
throw new ConfigurationException(
"Required configuration property not found: " + key,
key
);
}
return value;
}
}| Feature | Checked Exception | Unchecked Exception | User-Defined Exception |
|---|---|---|---|
| Parent Class | Exception |
RuntimeException |
Either Exception or RuntimeException |
| Compile-time Check | Yes | No | Depends on parent class |
| Must Handle/Declare | Yes | No | Depends on parent class |
| Use Case | Recoverable conditions | Programming errors | Application-specific errors |
| Examples | IOException, SQLException |
NullPointerException, ArithmeticException |
InsufficientFundsException, InvalidAgeException |
| When to Use | External failures you can recover from | Logic errors that shouldn't happen | Domain-specific error conditions |
- Be specific with catch blocks: Catch specific exceptions rather than generic
Exception
try {
// code
} catch (FileNotFoundException e) {
// Handle file not found
} catch (IOException e) {
// Handle other IO errors
}- Don't swallow exceptions: Always log or handle exceptions appropriately
// Bad
try {
riskyOperation();
} catch (Exception e) {
// Silent failure
}
// Good
try {
riskyOperation();
} catch (Exception e) {
logger.error("Operation failed", e);
// Handle appropriately
}- Use finally for cleanup: Always close resources in finally block or use try-with-resources
// Try-with-resources (Java 7+)
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}-
Don't use exceptions for flow control: Exceptions should be exceptional
-
Provide context in exception messages: Include relevant information to help debugging
throw new IllegalArgumentException("Invalid age: " + age + ". Must be between 0 and 150");- Document exceptions: Use
@throwsin Javadoc
/**
* Withdraws money from the account
* @param amount the amount to withdraw
* @throws InsufficientFundsException if balance is insufficient
*/
public void withdraw(double amount) throws InsufficientFundsException {
// implementation
}This guide covers the fundamental concepts of Java exceptions. Understanding when to use checked vs unchecked exceptions and how to create meaningful custom exceptions is crucial for writing robust Java applications.