Advanced Exception Handling
A single catch block can catch multiple exception types using "|": catch (IOException | SQLException e).
You can build your own hierarchy of custom exceptions — like a base AppException, extended into a specific PaymentFailedException — this organizes errors in large projects.
try {
riskyOperation();
} catch (IOException | SQLException e) {
System.out.println("Handled: " + e.getMessage());
}
class AppException extends Exception {}
class PaymentFailedException extends AppException {}- catch (A | B e) = multi-catch
- Custom exceptions can form their own hierarchy
- Organizes errors in large projects
Any class that implements the AutoCloseable interface (FileReader, Scanner, a database Connection, etc.) can be used in try-with-resources. Declare the resource inside try(...), and as soon as the block ends (whether normally or via an exception) close() is called automatically — no more need to manually write close() in a finally block.
In Java 9+, you can even declare the resource outside try() (if it's effectively final) and just write its name inside try() — a slightly cleaner syntax.
try (Scanner sc = new Scanner(System.in)) {
String input = sc.nextLine();
} // sc.close() automatically ho gaya
// Multiple resources bhi ek saath:
try (FileReader fr = new FileReader("a.txt"); FileWriter fw = new FileWriter("b.txt")) {
// dono automatically close honge, reverse order mein
}When creating a custom exception: give it a meaningful name (InsufficientBalanceException, not MyException or Exception1), extend Exception or RuntimeException (decide checked vs unchecked — detailed in the last point of this subtopic), and give it a constructor that accepts a message and calls super(message).
You can add extra fields to a custom exception too — like a "shortfallAmount" field on InsufficientBalanceException, so the code that catches it can use that extra info.
class InsufficientBalanceException extends RuntimeException {
final double shortfall;
InsufficientBalanceException(String message, double shortfall) {
super(message);
this.shortfall = shortfall;
}
}Sometimes one exception happens because of another — you can wrap the original exception as the "cause", so the original root-cause info isn't lost (the stack trace shows "Caused by:"). The pattern is new CustomException("msg", originalException) — the second argument is the "cause".
try {
readFile();
} catch (IOException e) {
throw new ServiceException("File load fail hua", e); // cause preserved
}Use a checked exception (one that extends Exception, not RuntimeException) when the caller *must* handle it, and recovery is possible (like file not found — you can retry or try an alternate path). The compiler forces you to either try-catch it or pass it further up with throws.
Use unchecked (RuntimeException) for programming errors that are bugs, where recovery normally isn't possible (like passing an invalid argument, or a null pointer) — you're not forced to try-catch these everywhere, the compiler doesn't require it.
| Checked | Unchecked (RuntimeException) | |
|---|---|---|
| Does the compiler force handling? | Yes | No |
| Example | IOException, SQLException | NullPointerException, IllegalArgumentException |
| When to use | Recoverable, external problems | Programming bugs, logic errors |