Table of Contents
- Introduction to Exception Handling
- Multiple Catch Blocks Syntax
- Exception Hierarchy and Ordering
- Practical Examples
- Multi-catch Block (Java 7+)
- Best Practices
- Common Pitfalls
- Real-World Scenarios
Introduction to Exception Handling
Exception handling is a crucial mechanism in Java that allows developers to handle runtime errors gracefully, maintaining normal application flow. Multiple catch blocks enable you to handle different types of exceptions in different ways, providing precise error handling for various exceptional conditions.
Multiple Catch Blocks Syntax
Basic Structure
try { // Code that may throw exceptions } catch (ExceptionType1 e1) { // Handle ExceptionType1 } catch (ExceptionType2 e2) { // Handle ExceptionType2 } catch (ExceptionType3 e3) { // Handle ExceptionType3 } finally { // Optional: Code that always executes } Exception Hierarchy and Ordering
Understanding Exception Hierarchy
// Exception Hierarchy (simplified) java.lang.Object ↳ java.lang.Throwable ↳ java.lang.Exception ↳ java.lang.RuntimeException (Unchecked) ↳ Other Checked Exceptions ↳ java.lang.Error
Correct Ordering Principle
Specific exceptions must come before general ones. The Java compiler enforces this rule to prevent unreachable code.
// CORRECT: Specific to general try { // risky code } catch (FileNotFoundException e) { // Handle file not found } catch (IOException e) { // Handle other IO exceptions } catch (Exception e) { // Handle any other exceptions } // COMPILER ERROR: Unreachable code try { // risky code } catch (Exception e) { // This would catch ALL exceptions } catch (IOException e) { // ERROR: Unreachable! // This block can never be executed } Practical Examples
Example 1: File Processing with Multiple Exceptions
import java.io.*; import java.nio.file.*; import java.util.*; public class FileProcessor { public void processFile(String filePath) { try { // Read all lines from file List<String> lines = Files.readAllLines(Paths.get(filePath)); // Process each line for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); int number = Integer.parseInt(line.trim()); System.out.println("Processed number: " + (number * 2)); } } catch (NoSuchFileException e) { System.err.println("Error: File '" + filePath + "' does not exist."); } catch (AccessDeniedException e) { System.err.println("Error: Access denied to file '" + filePath + "'."); } catch (NumberFormatException e) { System.err.println("Error: Invalid number format in file."); } catch (IOException e) { System.err.println("Error: General IO error - " + e.getMessage()); } catch (Exception e) { System.err.println("Error: Unexpected error - " + e.getMessage()); } } } Example 2: Database Operations
import java.sql.*; import java.util.*; public class DatabaseManager { private Connection connection; public void executeUserQuery(int userId, String query) { try { // Establish database connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "pass"); // Execute query PreparedStatement stmt = connection.prepareStatement(query); stmt.setInt(1, userId); ResultSet rs = stmt.executeQuery(); // Process results while (rs.next()) { System.out.println("User: " + rs.getString("username")); } } catch (SQLSyntaxErrorException e) { System.err.println("SQL Syntax Error: " + e.getMessage()); logError("SQL Syntax", e); } catch (SQLDataException e) { System.err.println("Data Error: Invalid data format"); logError("Data Format", e); } catch (SQLTimeoutException e) { System.err.println("Database timeout occurred"); retryOperation(); } catch (SQLException e) { System.err.println("General SQL Error: " + e.getErrorCode() + " - " + e.getMessage()); logError("General SQL", e); } catch (Exception e) { System.err.println("Unexpected error: " + e.getMessage()); logError("Unexpected", e); } finally { // Always close connection closeConnection(); } } private void logError(String type, Exception e) { // Implementation for error logging System.out.println("Logged " + type + " error: " + new Date()); } private void retryOperation() { // Implementation for retry logic System.out.println("Retrying operation..."); } private void closeConnection() { try { if (connection != null && !connection.isClosed()) { connection.close(); } } catch (SQLException e) { System.err.println("Error closing connection: " + e.getMessage()); } } } Example 3: Network Operations
import java.net.*; import java.io.*; public class NetworkClient { public void connectToServer(String host, int port) { Socket socket = null; try { // Create socket connection socket = new Socket(host, port); System.out.println("Connected to " + host + ":" + port); // Set timeout socket.setSoTimeout(5000); // Send data OutputStream output = socket.getOutputStream(); PrintWriter writer = new PrintWriter(output, true); writer.println("Hello Server!"); // Receive response InputStream input = socket.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(input)); String response = reader.readLine(); System.out.println("Server response: " + response); } catch (UnknownHostException e) { System.err.println("Error: Unknown host '" + host + "'"); } catch (ConnectException e) { System.err.println("Error: Connection refused to " + host + ":" + port); } catch (SocketTimeoutException e) { System.err.println("Error: Connection timeout after 5 seconds"); } catch (SSLHandshakeException e) { System.err.println("Error: SSL handshake failed"); } catch (IOException e) { System.err.println("Error: Network IO error - " + e.getMessage()); } finally { // Always close socket if (socket != null && !socket.isClosed()) { try { socket.close(); System.out.println("Connection closed"); } catch (IOException e) { System.err.println("Error closing socket: " + e.getMessage()); } } } } } Multi-catch Block (Java 7+)
Introduction to Multi-catch
Java 7 introduced the multi-catch block, allowing you to catch multiple exception types in a single catch block.
Multi-catch Syntax
try { // Code that may throw multiple exceptions } catch (ExceptionType1 | ExceptionType2 | ExceptionType3 e) { // Handle all specified exception types } Multi-catch Examples
// Example 1: File operations with multi-catch public void readConfigurationFile(String configPath) { try { Properties props = new Properties(); props.load(new FileInputStream(configPath)); String timeoutStr = props.getProperty("timeout"); int timeout = Integer.parseInt(timeoutStr); } catch (FileNotFoundException | SecurityException e) { System.err.println("File access error: " + e.getMessage()); } catch (NumberFormatException | IllegalArgumentException e) { System.err.println("Invalid configuration value: " + e.getMessage()); } catch (IOException e) { System.err.println("IO error reading configuration: " + e.getMessage()); } } // Example 2: Web service calls public void callWebService(String url) { try { URL serviceUrl = new URL(url); HttpURLConnection connection = (HttpURLConnection) serviceUrl.openConnection(); // Set request properties connection.setRequestMethod("GET"); connection.setConnectTimeout(3000); int responseCode = connection.getResponseCode(); System.out.println("Response Code: " + responseCode); } catch (MalformedURLException | ProtocolException e) { System.err.println("Invalid URL or protocol: " + e.getMessage()); } catch (SocketTimeoutException | ConnectException e) { System.err.println("Connection timeout or refused: " + e.getMessage()); retryWebServiceCall(url); } catch (IOException e) { System.err.println("Network error: " + e.getMessage()); } } Important Multi-catch Rules
// VALID: Exceptions in same hierarchy level catch (IOException | SQLException e) { } // VALID: Exceptions with common ancestor catch (FileNotFoundException | EOFException e) { } // Both extend IOException // INVALID: Exceptions in inheritance relationship catch (FileNotFoundException | IOException e) { } // COMPILER ERROR! // VALID: With final or effectively final variable catch (IOException | SQLException e) { // 'e' is effectively final logError(e); // Can use e // e = new IOException(); // ERROR - e is final } Best Practices
1. Order Specific to General
// GOOD try { // operations } catch (SpecificException e) { // handle specific case } catch (GeneralException e) { // handle general case } // BAD - compiler error try { // operations } catch (GeneralException e) { // handle general case } catch (SpecificException e) { // UNREACHABLE! // handle specific case } 2. Don't Catch Throwable
// AVOID - catches everything including Errors try { // code } catch (Throwable t) { // Too broad! // handling } // PREFER - catch specific exceptions try { // code } catch (SpecificException e) { // specific handling } catch (Exception e) { // general exception handling } 3. Use Multi-catch for Related Exceptions
// When handling is identical for multiple exceptions try { // database operations } catch (SQLSyntaxErrorException | SQLDataException e) { // Both are data-related errors logger.log("Data error: " + e.getMessage()); rollbackTransaction(); } 4. Don't Ignore Exceptions
// BAD - silent failure try { riskyOperation(); } catch (Exception e) { // Empty catch block - exception ignored! } // GOOD - at least log it try { riskyOperation(); } catch (Exception e) { logger.error("Operation failed", e); } // BETTER - handle appropriately try { riskyOperation(); } catch (SpecificException e) { recoverFromError(); } catch (AnotherException e) { notifyUser(); } 5. Use Finally for Cleanup
public void processResource() { Connection conn = null; PreparedStatement stmt = null; try { conn = getConnection(); stmt = conn.prepareStatement("SELECT * FROM users"); // ... process results } catch (SQLException e) { handleDatabaseError(e); } finally { // Always cleanup resources try { if (stmt != null) stmt.close(); if (conn != null) conn.close(); } catch (SQLException e) { logger.warn("Error closing resources", e); } } } Common Pitfalls
1. Overly Broad Catch Blocks
// PROBLEMATIC: Hides specific errors try { readFile(); parseData(); saveToDatabase(); } catch (Exception e) { // Too broad! System.out.println("Something went wrong"); } // BETTER: Specific handling try { readFile(); } catch (FileNotFoundException e) { createDefaultFile(); } catch (IOException e) { logger.error("File read error", e); } try { parseData(); } catch (ParseException e) { logger.error("Data parsing error", e); } try { saveToDatabase(); } catch (SQLException e) { logger.error("Database error", e); } 2. Resource Leakage
// RISKY: Potential resource leak try { FileInputStream fis = new FileInputStream("file.txt"); // process file fis.close(); // Might not execute if exception occurs } catch (IOException e) { // handle } // BETTER: Use try-with-resources (Java 7+) try (FileInputStream fis = new FileInputStream("file.txt")) { // process file // fis automatically closed } catch (IOException e) { // handle } 3. Exception Masking
// PROBLEM: Original exception masked try { riskyOperation(); } catch (Exception e) { throw new RuntimeException("Operation failed"); // Original exception lost! } // BETTER: Preserve original exception try { riskyOperation(); } catch (Exception e) { throw new RuntimeException("Operation failed", e); // Include cause } Real-World Scenarios
Scenario 1: Web Application Controller
@RestController public class UserController { @Autowired private UserService userService; @PostMapping("/users") public ResponseEntity<?> createUser(@RequestBody User user) { try { User createdUser = userService.createUser(user); return ResponseEntity.ok(createdUser); } catch (DuplicateUserException e) { return ResponseEntity.status(HttpStatus.CONFLICT) .body(new ErrorResponse("User already exists")); } catch (InvalidDataException e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(new ErrorResponse("Invalid user data: " + e.getMessage())); } catch (ServiceUnavailableException e) { return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) .body(new ErrorResponse("Service temporarily unavailable")); } catch (Exception e) { logger.error("Unexpected error creating user", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ErrorResponse("Internal server error")); } } } Scenario 2: Batch Data Processing
public class DataBatchProcessor { public void processBatch(List<String> dataLines) { int successCount = 0; int errorCount = 0; for (int i = 0; i < dataLines.size(); i++) { try { processSingleRecord(dataLines.get(i), i + 1); successCount++; } catch (DataFormatException e) { errorCount++; logger.warn("Invalid data format at line " + (i + 1) + ": " + e.getMessage()); continue; // Continue with next record } catch (DatabaseConstraintException e) { errorCount++; logger.error("Database constraint violation at line " + (i + 1), e); continue; } catch (ExternalServiceException e) { errorCount++; logger.error("External service failure at line " + (i + 1), e); // Maybe break or continue based on business logic if (e.isCritical()) { throw new BatchProcessingException("Critical failure", e); } continue; } catch (Exception e) { errorCount++; logger.error("Unexpected error processing line " + (i + 1), e); continue; } } logger.info("Batch processing completed: " + successCount + " successful, " + errorCount + " errors"); } private void processSingleRecord(String data, int lineNumber) throws DataFormatException, DatabaseConstraintException, ExternalServiceException { // Implementation details } } Scenario 3: Financial Transaction System
public class TransactionProcessor { public TransactionResult processTransaction(Transaction transaction) { try { validateTransaction(transaction); checkFraud(transaction); processPayment(transaction); updateAccounts(transaction); return TransactionResult.success(transaction.getId()); } catch (InvalidAmountException e) { logger.warn("Invalid amount in transaction: " + transaction.getId()); return TransactionResult.failure("INVALID_AMOUNT", e.getMessage()); } catch (InsufficientFundsException e) { logger.warn("Insufficient funds for transaction: " + transaction.getId()); return TransactionResult.failure("INSUFFICIENT_FUNDS", e.getMessage()); } catch (FraudDetectionException e) { logger.error("Fraud detected in transaction: " + transaction.getId()); blockAccount(transaction.getFromAccount()); return TransactionResult.failure("FRAUD_DETECTED", "Transaction blocked"); } catch (NetworkTimeoutException e) { logger.error("Network timeout processing transaction: " + transaction.getId()); // Mark for retry retryService.scheduleRetry(transaction); return TransactionResult.failure("NETWORK_ERROR", "Will retry"); } catch (DatabaseException e) { logger.error("Database error processing transaction: " + transaction.getId(), e); // Critical error - need manual intervention alertAdministrator(transaction, e); return TransactionResult.failure("SYSTEM_ERROR", "Contact support"); } catch (Exception e) { logger.error("Unexpected error processing transaction: " + transaction.getId(), e); return TransactionResult.failure("UNKNOWN_ERROR", "System error"); } } } Summary
Multiple catch blocks in Java provide a powerful mechanism for handling different types of exceptions appropriately. Key takeaways:
- Order matters - specific exceptions before general ones
- Use multi-catch for related exceptions with identical handling
- Avoid overly broad catch blocks that hide problems
- Always cleanup resources using finally blocks or try-with-resources
- Log appropriately for debugging and monitoring
- Consider the business context when deciding how to handle exceptions
Proper exception handling with multiple catch blocks makes your applications more robust, maintainable, and user-friendly.