🧯
Exceptions

Advanced Exception Handling

Multi-catch & Custom Hierarchy
💡 Multi-catch is one security guard who handles different kinds of troublemakers (exceptions) at a single gate — no need to build a separate gate for each.

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 {}
🧯
Multi-catch is one security guard who handles different kinds of troublemakers (exceptions) at a single gate — no need to build a separate gate for each.
1 / 4
⚡ Quick Recap
  • catch (A | B e) = multi-catch
  • Custom exceptions can form their own hierarchy
  • Organizes errors in large projects
On this page (4 subtopics)

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.

CheckedUnchecked (RuntimeException)
Does the compiler force handling?YesNo
ExampleIOException, SQLExceptionNullPointerException, IllegalArgumentException
When to useRecoverable, external problemsProgramming bugs, logic errors