Java Multiple catch Blocks and Multi-catch Syntax
When a try block might throw different types of exceptions, you can handle each one separately with multiple catch blocks or combine unrelated ones with multi-catch syntax.
Multiple catch Blocks
try {
String text = readFile("data.txt");
int value = Integer.parseInt(text.trim());
int result = 100 / value;
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
} catch (NumberFormatException e) {
System.out.println("Invalid number in file: " + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("Division error: " + e.getMessage());
}
Each catch handles a specific failure mode with a specific response.
Order Matters — Specific First
Java checks catch blocks top to bottom and enters the first matching one:
// ✅ CORRECT — specific before general
try {
riskyOperation();
} catch (FileNotFoundException e) {
// handles FileNotFoundException specifically
} catch (IOException e) {
// handles all other IOExceptions
} catch (Exception e) {
// fallback for anything else
}
// ❌ COMPILE ERROR — general before specific
try {
riskyOperation();
} catch (IOException e) {
// catches everything, including FileNotFoundException
} catch (FileNotFoundException e) {
// unreachable — compiler error!
}
If a parent class appears before a child class, the child’s catch block is unreachable and the code won’t compile.
Multi-catch — Java 7+
Handle multiple unrelated exceptions the same way:
try {
processData();
} catch (IOException | SQLException e) {
logger.error("Data processing failed", e);
throw new ServiceException("Processing error", e);
}
Rules for Multi-catch
| Rule | Explanation |
|---|---|
Types separated by | |
Pipe symbol between exception types |
| No parent-child pairs | IOException | FileNotFoundException — compile error |
| Variable is effectively final | You cannot reassign e |
| Common parent type used | e is the common type of all listed exceptions |
// ❌ COMPILE ERROR — FileNotFoundException is a subclass of IOException
catch (IOException | FileNotFoundException e) { }
// ✅ CORRECT — unrelated exception types
catch (IOException | NumberFormatException e) { }
Why Is e Effectively Final?
catch (IOException | SQLException e) {
// e could be either type at runtime
// e = new IOException(); // ❌ COMPILE ERROR — cannot reassign
logger.error("Error", e);
}
Since e might be different types, allowing reassignment could violate type safety.
Multi-catch vs Multiple catch — When to Use Each
| Use Case | Approach |
|---|---|
| Same handling for different exceptions | Multi-catch (|) |
| Different handling per exception | Multiple catch blocks |
| Need to access exception-specific methods | Multiple catch blocks |
| Reducing boilerplate | Multi-catch (|) |
Same Handling → Multi-catch
// Before (repetitive)
catch (IOException e) {
logAndAlert(e);
} catch (SQLException e) {
logAndAlert(e);
}
// After (clean)
catch (IOException | SQLException e) {
logAndAlert(e);
}
Different Handling → Multiple catch
catch (FileNotFoundException e) {
useDefaultConfig(); // specific recovery
} catch (IOException e) {
throw new ServiceException("IO failure", e); // different response
}
Combining Both
try {
loadAndProcess(filePath);
} catch (FileNotFoundException e) {
// Specific handling
System.out.println("File not found, creating default...");
createDefaultFile(filePath);
} catch (IOException | ParseException e) {
// Same handling for these two
logger.error("Processing failed: " + e.getMessage());
throw new DataException("Cannot process " + filePath, e);
} catch (Exception e) {
// Catch-all fallback
logger.error("Unexpected error", e);
throw e;
}
Accessing Exception-Specific Methods
With multi-catch, you can only access methods from the common parent:
catch (IOException | SQLException e) {
e.getMessage(); // ✅ Defined in Throwable
e.getErrorCode(); // ❌ Only on SQLException
}
If you need exception-specific methods, use separate catch blocks:
catch (SQLException e) {
int code = e.getErrorCode(); // ✅ SQLException-specific
String state = e.getSQLState(); // ✅ SQLException-specific
} catch (IOException e) {
// handle differently
}
Pattern: Catch, Log, Rethrow
try {
return orderService.process(order);
} catch (ValidationException e) {
logger.warn("Validation failed for order {}: {}", order.getId(), e.getMessage());
throw e; // let caller handle
} catch (PaymentException | InventoryException e) {
logger.error("Order processing failed for {}", order.getId(), e);
throw new OrderFailedException("Cannot process order", e);
} catch (Exception e) {
logger.error("Unexpected error processing order {}", order.getId(), e);
throw e;
}
Pattern: Fallback Chain
public Config loadConfig() {
try {
return loadFromFile("config.yml");
} catch (FileNotFoundException e) {
logger.info("Config file not found, trying environment");
}
try {
return loadFromEnvironment();
} catch (ConfigException e) {
logger.info("Environment config not found, using defaults");
}
return Config.defaults();
}
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
| Parent before child | Compile error (unreachable) | Specific first |
| Related types in multi-catch | Compile error | Use only unrelated types |
Catching Exception first |
Catches everything | Put last or remove |
| Empty catch blocks | Silent failures | Log and rethrow |
| Too many catch blocks | Hard to read | Group with multi-catch, extract methods |
Reassigning e in multi-catch |
Compile error | e is effectively final |
Key Takeaways
- Order catch blocks from most specific to most general — compiler enforces this.
- Use multi-catch (
catch (A | B e)) for unrelated exceptions with identical handling. - Multi-catch variable is effectively final — you can’t reassign it.
- Multi-catch types must be unrelated — no parent-child pairs.
- Use separate catches when you need exception-specific methods or different handling logic.
What’s Next?
Next: try-with-resources — automatic resource management, AutoCloseable, and suppressed exceptions.