Lesson 32 / 38

Custom Exceptions & Multi-Catch

Design your own exception types that carry meaningful context, and handle several exception types in one concise catch clause.

A custom checked exception

Extend Exception for a checked exception callers must handle, or RuntimeException for an unchecked one signalling a programming error. Always chain the original cause so the stack trace stays complete.

public class InsufficientFundsException extends Exception {
    private final double shortfall;

    public InsufficientFundsException(double shortfall) {
        super("short by " + shortfall);
        this.shortfall = shortfall;
    }
    public double getShortfall() { return shortfall; }
}

// caller:
void withdraw(double amt) throws InsufficientFundsException {
    if (amt > balance) throw new InsufficientFundsException(amt - balance);
    balance -= amt;
}

Multi-catch

When two exception types need the same handling, catch both with | instead of duplicating the block. The caught variable is effectively final and typed as the common supertype.

try {
    process(file);
} catch (IOException | ParseException e) {
    log.error("could not process file", e);
    throw new RuntimeException(e);
}

Rethrow with context, don't hide it

Catching a low-level exception just to throw a more meaningful one is good practice — as long as you pass the original as the cause (new ServiceException("...", e)), never dropping it, or the real root cause becomes invisible in production logs.

Quick check

Quick check: Which base class should a custom exception extend if callers should NOT be forced to declare or catch it?

  • Exception
  • Throwable
  • RuntimeException
  • Error
Answer

RuntimeException — RuntimeException (and its subclasses) are unchecked — the compiler does not require a throws declaration or a catch block.