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
-
Checked Exceptions
public class CheckedExceptionExample { public void readFile() throws IOException { FileReader file = new FileReader("file.txt"); // Must handle or declare IOException } }
-
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
-
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; } }
-
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
-
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()); } } } } }
-
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
-
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); } } }
-
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
- 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
-
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(); } }
-
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
-
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 } }
-
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
-
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); } } }
-
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
-
Multi-Catch (Java 7+)
public class MultiCatchExample { public void process() { try { // Risky operations } catch (IOException | SQLException e) { // Handle both exceptions logError(e); } } }
-
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
-
Testing Exception Throwing
@Test(expected = ValidationException.class) public void testInvalidAge() { UserService service = new UserService(); service.createUser("John", 15); // Should throw ValidationException }
-
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()); } }