Overview
In Java, exceptions are divided into two main categories: Checked Exceptions and Unchecked Exceptions. This distinction is fundamental to Java's exception handling mechanism.
Exception Hierarchy
Throwable ├── Error (Unchecked) │ ├── VirtualMachineError │ │ ├── OutOfMemoryError │ │ └── StackOverflowError │ └── ... ├── Exception │ ├── RuntimeException (Unchecked) │ │ ├── NullPointerException │ │ ├── IllegalArgumentException │ │ ├── ArrayIndexOutOfBoundsException │ │ └── ... │ └── Checked Exceptions │ ├── IOException │ ├── SQLException │ ├── ClassNotFoundException │ └── ...
Checked Exceptions
Definition
Checked exceptions are exceptions that are checked at compile-time. The compiler ensures that these exceptions are either caught or declared in the method signature.
Characteristics
- Must be handled explicitly using try-catch or declared with throws
- Represent conditions that a reasonable application might want to catch
- Extend
Exceptionbut notRuntimeException
Common Checked Exceptions
import java.io.*;
import java.sql.*;
public class CheckedExceptionsExamples {
// IOException - Must be handled or declared
public void readFile() throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
reader.close();
}
// SQLException - Must be handled or declared
public void databaseOperation() throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db");
Statement stmt = conn.createStatement();
stmt.executeQuery("SELECT * FROM users");
}
// ClassNotFoundException - Must be handled or declared
public void loadClass() throws ClassNotFoundException {
Class.forName("com.example.MyClass");
}
// FileNotFoundException - Must be handled or declared
public void openFile() throws FileNotFoundException {
FileInputStream fis = new FileInputStream("nonexistent.txt");
}
}
Handling Checked Exceptions
public class CheckedExceptionHandling {
// Method 1: Declare in throws clause
public void methodWithThrows() throws IOException {
FileReader file = new FileReader("test.txt");
}
// Method 2: Handle with try-catch
public void methodWithTryCatch() {
try {
FileReader file = new FileReader("test.txt");
System.out.println("File opened successfully");
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
}
}
// Method 3: Combination
public void processFile() throws IOException {
try {
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
// Process line
} catch (FileNotFoundException e) {
System.err.println("Logging error: " + e.getMessage());
throw e; // Re-throw
}
}
}
Unchecked Exceptions
Definition
Unchecked exceptions are exceptions that are not checked at compile-time. They extend RuntimeException or Error.
Characteristics
- Not required to be caught or declared
- Represent programming errors or system problems
- Extend
RuntimeExceptionorError
Common Unchecked Exceptions
public class UncheckedExceptionsExamples {
public void demonstrateUncheckedExceptions() {
// NullPointerException
String str = null;
// str.length(); // This would throw NullPointerException
// ArrayIndexOutOfBoundsException
int[] numbers = {1, 2, 3};
// int value = numbers[5]; // This would throw ArrayIndexOutOfBoundsException
// IllegalArgumentException
// setAge(-5); // This would throw IllegalArgumentException
// NumberFormatException
// int num = Integer.parseInt("abc"); // This would throw NumberFormatException
// ClassCastException
Object obj = "Hello";
// Integer num = (Integer) obj; // This would throw ClassCastException
}
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative: " + age);
}
// Set age
}
}
Error Examples
public class ErrorExamples {
public void causeStackOverflow() {
causeStackOverflow(); // Recursive call without base case
}
public void causeOutOfMemory() {
List<byte[]> memoryHog = new ArrayList<>();
while (true) {
memoryHog.add(new byte[1024 * 1024]); // 1MB chunks
}
}
}
Key Differences
| Aspect | Checked Exceptions | Unchecked Exceptions |
|---|---|---|
| Compile-time Checking | Yes | No |
| Handling Requirement | Must be handled or declared | Optional |
| Inheritance | Extend Exception | Extend RuntimeException or Error |
| Recovery | Usually recoverable | Usually programming errors |
| Examples | IOException, SQLException | NullPointerException, IllegalArgumentException |
Best Practices
When to Use Checked Exceptions
public class CheckedExceptionBestPractices {
// Use checked exceptions for recoverable conditions
public void loadConfiguration(String filename) throws ConfigurationException {
try {
Properties props = new Properties();
props.load(new FileInputStream(filename));
} catch (IOException e) {
throw new ConfigurationException("Failed to load configuration: " + filename, e);
}
}
// Provide meaningful exception messages
public void validateUserInput(String input) throws ValidationException {
if (input == null || input.trim().isEmpty()) {
throw new ValidationException("Input cannot be null or empty");
}
if (input.length() < 5) {
throw new ValidationException("Input must be at least 5 characters long");
}
}
}
// Custom checked exception
class ConfigurationException extends Exception {
public ConfigurationException(String message) {
super(message);
}
public ConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}
class ValidationException extends Exception {
public ValidationException(String message) {
super(message);
}
}
When to Use Unchecked Exceptions
public class UncheckedExceptionBestPractices {
// Use unchecked exceptions for programming errors
public void processOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("Order cannot be null");
}
if (order.getItems() == null || order.getItems().isEmpty()) {
throw new IllegalStateException("Order must have at least one item");
}
// Process order
}
// Precondition validation
public void withdrawMoney(BankAccount account, double amount) {
if (account == null) {
throw new NullPointerException("Account cannot be null");
}
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive: " + amount);
}
if (amount > account.getBalance()) {
throw new InsufficientFundsException("Insufficient funds");
}
account.withdraw(amount);
}
}
// Custom unchecked exception
class InsufficientFundsException extends RuntimeException {
public InsufficientFundsException(String message) {
super(message);
}
}
Exception Handling Patterns
1. Try-with-resources (Java 7+)
public class TryWithResourcesExample {
// Automatic resource management for checked exceptions
public void readFile(String filename) {
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
// Handle IOException
System.err.println("Error reading file: " + e.getMessage());
}
}
}
2. Exception Translation
public class ExceptionTranslation {
// Translate lower-level exceptions to higher-level abstractions
public User loadUser(String userId) throws UserServiceException {
try {
return userRepository.findById(userId);
} catch (SQLException e) {
throw new UserServiceException("Failed to load user: " + userId, e);
} catch (IOException e) {
throw new UserServiceException("Configuration error while loading user", e);
}
}
}
class UserServiceException extends Exception {
public UserServiceException(String message) {
super(message);
}
public UserServiceException(String message, Throwable cause) {
super(message, cause);
}
}
3. Exception Chaining
public class ExceptionChainingExample {
public void processData(String data) throws ProcessingException {
try {
// Some processing that might throw IOException
parseData(data);
} catch (IOException e) {
// Chain the exception
throw new ProcessingException("Failed to process data: " + data, e);
}
}
private void parseData(String data) throws IOException {
// Parsing logic
}
}
class ProcessingException extends Exception {
public ProcessingException(String message) {
super(message);
}
public ProcessingException(String message, Throwable cause) {
super(message, cause);
}
}
Common Mistakes to Avoid
public class ExceptionMistakes {
// ❌ DON'T: Empty catch block (swallowing exceptions)
public void badPractice1() {
try {
// Some code that might throw exception
} catch (Exception e) {
// Empty - exception completely ignored!
}
}
// ❌ DON'T: Catch generic Exception
public void badPractice2() {
try {
// Some code
} catch (Exception e) {
// Too broad - might catch unexpected exceptions
}
}
// ❌ DON'T: Throw Exception in method signature
public void badPractice3() throws Exception {
// Too vague - callers don't know what to expect
}
// ✅ DO: Be specific about exceptions
public void goodPractice1() throws IOException, SQLException {
// Caller knows exactly what to handle
}
// ✅ DO: Log exceptions properly
public void goodPractice2() {
try {
// Some code
} catch (SpecificException e) {
logger.error("Specific error occurred", e);
// Handle or re-throw appropriately
}
}
// ✅ DO: Use finally for cleanup
public void goodPractice3() {
Connection conn = null;
try {
conn = getConnection();
// Use connection
} catch (SQLException e) {
logger.error("Database error", e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
logger.error("Error closing connection", e);
}
}
}
}
}
Performance Considerations
- Checked exceptions have minimal performance overhead
- Unchecked exceptions are relatively fast when not thrown
- Stack trace generation is expensive - avoid exceptions for normal control flow
- Use exceptions for exceptional circumstances only
Summary
- Use Checked Exceptions for recoverable conditions that callers should handle
- Use Unchecked Exceptions for programming errors and precondition violations
- Always document exceptions that your methods can throw
- Provide meaningful error messages and context
- Don't use exceptions for normal control flow
- Consider the caller's perspective when choosing exception types
Understanding the distinction between checked and unchecked exceptions is crucial for writing robust, maintainable Java applications that handle errors appropriately.