💥 Exceptions & Errors · Intermediate

Custom exceptions in Java

Extending Exception or RuntimeException, adding context.

🧩 The mystery"Something went wrong" — the least helpful error message ever. What if your exception could say *which* order failed and *how much* money was missing?

Pick a parent

Create your own exception by extending **Exception — making it checked — or RuntimeException — making it unchecked**. It inherits that status, so a RuntimeException subclass never forces callers to catch or declare it. Name it ...Exception.

Pass it up to super

Constructors should pass the message — and ideally the cause — to **super(message) or super(message, cause)**. Throwable stores them for getMessage() and getCause(). Add fields for context, like the missing amount.

class InsufficientFundsException
        extends Exception {
    final long missing;
    InsufficientFundsException(long missing) {
        super("short by " + missing);
        this.missing = missing;
    }
}
🔮 Predict it

Your own message

What does this print?

class SoldOut extends RuntimeException {
    SoldOut(String s) { super(s + " is gone"); }
}
void main() {
    try {
        throw new SoldOut("Cake");
    } catch (RuntimeException e) {
        System.out.println(e.getMessage());
    }
}
  1. Cake is gone
  2. null
  3. SoldOut
Show the answer

The constructor passed the full text to super(...), which stores it in Throwable, so getMessage() returns it. And a SoldOut is a RuntimeException, so that catch matches.

⚠️ The trap

The lost cause

This constructor accepts a cause and then drops it: super(msg) ignores cause, so getCause() returns null and the root problem vanishes from stack traces. Use **super(msg, cause)**.

class DataException extends Exception {
    DataException(String msg, Throwable cause) {
        super(msg);   // cause is lost!
    }
}
🤔 Think first

Checked by inheritance

class PaymentDeclined extends Exception { }. A method throws it without catching or declaring it. What happens?

Think about it, then reveal the answer

Compile error. PaymentDeclined extends Exception (not RuntimeException), so it's checked: the method must catch it or declare throws PaymentDeclined.

💼 In the real world

Precise types pay off

Throwing InsufficientFundsException instead of new RuntimeException("insufficient funds") lets handlers catch exactly that case and read structured data (e.missing) instead of parsing message strings. Web frameworks map such types straight to HTTP responses, like 402 or 409.

Key takeaways

  1. extends Exception → checked; extends RuntimeException → unchecked
  2. Call super(message) or super(message, cause)
  3. Add context fields (ids, amounts) for handlers and logs
  4. Name it ...Exception
🤯 Did you know?

Creating an exception is costly mainly because it records the stack trace. Performance-critical code sometimes overrides fillInStackTrace() to skip that work.

Practice questions

What does this print?

class OutOfStock extends RuntimeException {
    OutOfStock(String item) { super(item + " sold out"); }
}
void main() {
    try {
        throw new OutOfStock("Tea");
    } catch (RuntimeException e) {
        System.out.println(e.getMessage());
    }
}
  1. Tea sold out
  2. null
  3. OutOfStock
  4. Tea
Check your answer

Tea sold out. The constructor passes the message to super, which stores it in Throwable. getMessage() returns it.

What does this print?

class OverdrawnException extends Exception { }
void withdraw() {
    throw new OverdrawnException();
}
void main() {
    withdraw();
}
  1. Throws OverdrawnException
  2. Compile error
  3. Prints nothing
Check your answer

Compile error. OverdrawnException extends Exception, so it's checked. withdraw() must catch it or declare throws OverdrawnException.

Next: who closes the file when your code explodes halfway through reading it? try-with-resources.