Skip to content

Exception Handling and Custom Exceptions in Java

Question

Explain the concept of exception handling in Java, including checked and unchecked exceptions, custom exceptions, and best practices for exception handling. How do you create and use custom exceptions effectively?

Answer

Exception handling in Java provides a robust mechanism to handle runtime errors and exceptional conditions. It allows for graceful error recovery and maintains program flow even when errors occur.

Types of Exceptions

  1. Checked Exceptions

    public class CheckedExceptionExample {
        public void readFile() throws IOException {
            FileReader file = new FileReader("file.txt");
            // Must handle or declare IOException
        }
    }
    

  2. Unchecked Exceptions

    public class UncheckedExceptionExample {
        public void divide(int a, int b) {
            if (b == 0) {
                throw new ArithmeticException("Division by zero");
            }
            int result = a / b;
        }
    }
    

Custom Exceptions

  1. Basic Custom Exception

    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; }
    }
    

  2. Custom Runtime Exception

    public class InvalidAgeException extends RuntimeException {
        private int age;
    
        public InvalidAgeException(int age) {
            super("Invalid age: " + age);
            this.age = age;
        }
    
        public int getAge() { return age; }
    }
    

Exception Handling Best Practices

  1. Try-Catch-Finally

    public class ResourceHandler {
        public void processFile() {
            FileReader reader = null;
            try {
                reader = new FileReader("file.txt");
                // Process file
            } catch (FileNotFoundException e) {
                System.err.println("File not found: " + e.getMessage());
            } catch (IOException e) {
                System.err.println("Error reading file: " + e.getMessage());
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        System.err.println("Error closing file: " + e.getMessage());
                    }
                }
            }
        }
    }
    

  2. Try-With-Resources (Java 7+)

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

Exception Chaining

  1. Cause Exception

    public class DatabaseException extends Exception {
        public DatabaseException(String message, Throwable cause) {
            super(message, cause);
        }
    }
    
    public class DatabaseHandler {
        public void connect() throws DatabaseException {
            try {
                // Database connection code
            } catch (SQLException e) {
                throw new DatabaseException("Failed to connect to database", e);
            }
        }
    }
    

  2. Exception Wrapping

    public class ServiceException extends Exception {
        public ServiceException(String message, Throwable cause) {
            super(message, cause);
        }
    }
    
    public class UserService {
        public void processUser(String userId) throws ServiceException {
            try {
                // Process user
            } catch (DatabaseException e) {
                throw new ServiceException("Failed to process user: " + userId, e);
            }
        }
    }
    

Exception Hierarchy

  1. Custom Exception Hierarchy
    public abstract class BusinessException extends Exception {
        private String errorCode;
    
        protected BusinessException(String message, String errorCode) {
            super(message);
            this.errorCode = errorCode;
        }
    
        public String getErrorCode() { return errorCode; }
    }
    
    public class ValidationException extends BusinessException {
        public ValidationException(String message) {
            super(message, "VAL_001");
        }
    }
    
    public class AuthenticationException extends BusinessException {
        public AuthenticationException(String message) {
            super(message, "AUTH_001");
        }
    }
    

Exception Handling Patterns

  1. Exception Handler Pattern

    public interface ExceptionHandler<T extends Exception> {
        void handle(T exception);
    }
    
    public class ValidationExceptionHandler implements ExceptionHandler<ValidationException> {
        @Override
        public void handle(ValidationException e) {
            // Handle validation exception
            logError(e);
            notifyUser(e);
        }
    }
    
    public class AuthenticationExceptionHandler implements ExceptionHandler<AuthenticationException> {
        @Override
        public void handle(AuthenticationException e) {
            // Handle authentication exception
            logError(e);
            redirectToLogin();
        }
    }
    

  2. Exception Translator Pattern

    public class ExceptionTranslator {
        public static ServiceException translate(Exception e) {
            if (e instanceof SQLException) {
                return new DatabaseException("Database error", e);
            } else if (e instanceof IOException) {
                return new FileProcessingException("File processing error", e);
            }
            return new ServiceException("Unexpected error", e);
        }
    }
    

Best Practices

  1. Specific Exception Types

    public class ExceptionExample {
        // Bad - Too generic
        public void process() throws Exception {
            // Code
        }
    
        // Good - Specific exceptions
        public void process() throws IOException, SQLException {
            // Code
        }
    }
    

  2. Meaningful Messages

    public class MessageExample {
        // Bad - Unclear message
        throw new Exception("Error occurred");
    
        // Good - Clear message
        throw new ValidationException("Age must be between 18 and 65");
    }
    

Common Pitfalls

  1. Swallowing Exceptions

    public class BadExceptionHandling {
        // Bad - Swallowing exception
        public void process() {
            try {
                // Risky operation
            } catch (Exception e) {
                // Do nothing
            }
        }
    
        // Good - Proper handling
        public void process() throws ServiceException {
            try {
                // Risky operation
            } catch (Exception e) {
                throw new ServiceException("Operation failed", e);
            }
        }
    }
    

  2. Catching Throwable

    public class ThrowableExample {
        // Bad - Catching Throwable
        try {
            // Code
        } catch (Throwable t) {
            // Too broad
        }
    
        // Good - Catch specific exceptions
        try {
            // Code
        } catch (IOException | SQLException e) {
            // Handle specific exceptions
        }
    }
    

Modern Java Features

  1. Multi-Catch (Java 7+)

    public class MultiCatchExample {
        public void process() {
            try {
                // Risky operations
            } catch (IOException | SQLException e) {
                // Handle both exceptions
                logError(e);
            }
        }
    }
    

  2. Try-With-Resources with Multiple Resources

    public class MultipleResources {
        public void process() {
            try (FileReader reader = new FileReader("input.txt");
                 FileWriter writer = new FileWriter("output.txt")) {
                // Process files
            } catch (IOException e) {
                // Handle exception
            }
        }
    }
    

Testing Exceptions

  1. Testing Exception Throwing

    @Test(expected = ValidationException.class)
    public void testInvalidAge() {
        UserService service = new UserService();
        service.createUser("John", 15);  // Should throw ValidationException
    }
    

  2. Testing Exception Messages

    @Test
    public void testExceptionMessage() {
        try {
            throw new InsufficientFundsException(100.0, 200.0);
        } catch (InsufficientFundsException e) {
            assertEquals("Insufficient funds. Balance: 100.00, Required: 200.00", 
                        e.getMessage());
        }
    }