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.