Skip to content

Exception Handling in Java

Question

Explain the exception handling mechanism in Java. How do you create custom exceptions, handle multiple exceptions, and use try-with-resources? What are the best practices for exception handling?

Answer

Exception handling in Java provides a robust mechanism to handle runtime errors and exceptional conditions. It uses a combination of try-catch blocks, throws declarations, and custom exceptions to manage error scenarios gracefully.

Basic Exception Handling

  1. Try-Catch Block

    public class FileProcessor {
        public void readFile(String path) {
            try {
                File file = new File(path);
                FileReader reader = new FileReader(file);
                // Process file
            } catch (FileNotFoundException e) {
                System.err.println("File not found: " + e.getMessage());
            } catch (IOException e) {
                System.err.println("Error reading file: " + e.getMessage());
            }
        }
    }
    

  2. Multiple Exception Handling

    public class DataProcessor {
        public void processData(String data) {
            try {
                int number = Integer.parseInt(data);
                double result = Math.sqrt(number);
                System.out.println("Result: " + result);
            } catch (NumberFormatException | IllegalArgumentException e) {
                System.err.println("Invalid input: " + e.getMessage());
            }
        }
    }
    

Custom Exceptions

  1. Creating Custom Exceptions

    public class InsufficientFundsException extends Exception {
        private double balance;
        private double amount;
    
        public InsufficientFundsException(double balance, double amount) {
            super(String.format("Insufficient funds. Balance: %.2f, Required: %.2f",
                balance, amount));
            this.balance = balance;
            this.amount = amount;
        }
    
        public double getBalance() { return balance; }
        public double getAmount() { return amount; }
    }
    
    public class BankAccount {
        private double balance;
    
        public void withdraw(double amount) throws InsufficientFundsException {
            if (amount > balance) {
                throw new InsufficientFundsException(balance, amount);
            }
            balance -= amount;
        }
    }
    

  2. Exception Hierarchy

    public class BankingException extends Exception {
        public BankingException(String message) {
            super(message);
        }
    }
    
    public class InsufficientFundsException extends BankingException {
        public InsufficientFundsException(String message) {
            super(message);
        }
    }
    
    public class InvalidAccountException extends BankingException {
        public InvalidAccountException(String message) {
            super(message);
        }
    }
    

Try-With-Resources

  1. Basic Try-With-Resources

    public class ResourceManager {
        public void processFile(String path) {
            try (FileReader reader = new FileReader(path);
                 BufferedReader bufferedReader = new BufferedReader(reader)) {
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    processLine(line);
                }
            } catch (IOException e) {
                System.err.println("Error processing file: " + e.getMessage());
            }
        }
    }
    

  2. Multiple Resources

    public class DatabaseManager {
        public void processData() {
            try (Connection conn = DriverManager.getConnection(url, user, password);
                 Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
                while (rs.next()) {
                    processRow(rs);
                }
            } catch (SQLException e) {
                System.err.println("Database error: " + e.getMessage());
            }
        }
    }
    

Exception Propagation

  1. Throws Declaration
    public class DataValidator {
        public void validateData(String data) throws ValidationException {
            if (data == null || data.trim().isEmpty()) {
                throw new ValidationException("Data cannot be empty");
            }
            if (!data.matches("[0-9]+")) {
                throw new ValidationException("Data must contain only numbers");
            }
        }
    }
    
    public class DataProcessor {
        public void process(String data) {
            try {
                validateData(data);
                // Process data
            } catch (ValidationException e) {
                System.err.println("Validation failed: " + e.getMessage());
            }
        }
    }
    

Best Practices

  1. Exception Chaining

    public class ServiceException extends Exception {
        public ServiceException(String message, Throwable cause) {
            super(message, cause);
        }
    }
    
    public class DataService {
        public void processData() {
            try {
                // Some operation that might fail
                throw new IOException("File not found");
            } catch (IOException e) {
                throw new ServiceException("Failed to process data", e);
            }
        }
    }
    

  2. Specific Exception Handling

    public class ExceptionHandler {
        public void handleException(Exception e) {
            if (e instanceof SQLException) {
                handleDatabaseError((SQLException) e);
            } else if (e instanceof IOException) {
                handleIOError((IOException) e);
            } else {
                handleGenericError(e);
            }
        }
    
        private void handleDatabaseError(SQLException e) {
            // Handle database-specific errors
        }
    
        private void handleIOError(IOException e) {
            // Handle IO-specific errors
        }
    }
    

Common Use Cases

  1. Resource Cleanup

    public class ResourceCleanup {
        private Connection connection;
    
        public void process() {
            try {
                connection = getConnection();
                // Process data
            } catch (SQLException e) {
                handleError(e);
            } finally {
                closeConnection();
            }
        }
    
        private void closeConnection() {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    // Log error but don't throw
                }
            }
        }
    }
    

  2. Validation

    public class UserValidator {
        public void validateUser(User user) throws ValidationException {
            if (user == null) {
                throw new ValidationException("User cannot be null");
            }
            if (user.getName() == null || user.getName().trim().isEmpty()) {
                throw new ValidationException("Name is required");
            }
            if (user.getAge() < 0) {
                throw new ValidationException("Age cannot be negative");
            }
        }
    }
    

Testing

  1. Testing Exception Scenarios
    @Test(expected = InsufficientFundsException.class)
    public void testInsufficientFunds() throws InsufficientFundsException {
        BankAccount account = new BankAccount(100.0);
        account.withdraw(200.0);
    }
    
    @Test
    public void testExceptionMessage() {
        try {
            validateUser(new User(null, -1));
            fail("Should have thrown ValidationException");
        } catch (ValidationException e) {
            assertTrue(e.getMessage().contains("Name is required"));
        }
    }
    

Common Pitfalls

  1. Swallowing Exceptions

    // Bad - Swallowing exception
    public void badPractice() {
        try {
            // Some operation
        } catch (Exception e) {
            // Do nothing
        }
    }
    
    // Good - Logging exception
    public void goodPractice() {
        try {
            // Some operation
        } catch (Exception e) {
            logger.error("Operation failed", e);
            // Consider rethrowing or handling appropriately
        }
    }
    

  2. Catching Generic Exception

    // Bad - Catching generic Exception
    public void badPractice() {
        try {
            // Some operation
        } catch (Exception e) {
            // Handle all exceptions the same way
        }
    }
    
    // Good - Catching specific exceptions
    public void goodPractice() {
        try {
            // Some operation
        } catch (SQLException e) {
            handleDatabaseError(e);
        } catch (IOException e) {
            handleIOError(e);
        }
    }