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