Matheion

MA-JAVA0 · Module 20

Custom exception types and hierarchies

Topics: extends Exception · extends RuntimeException · Chaining · When checked

Overview

Session 20 — Custom exception types and hierarchies

Standard types like IOException and IllegalArgumentException cover many cases, yet libraries often introduce domain-specific exceptions so callers can catch precise faults without string matching. This session covers extends Exception versus extends RuntimeException, constructors that attach messages and causes, and when checked exceptions still earn their place.

Time: about 85 minutes.

Concepts

1. Checked vs unchecked custom types

  • extends Exception — checked: callers must catch or throws—use when recovery is common and you want compile-time enforcement (file format, protocol violations).
  • extends RuntimeException — unchecked: for contract breaks similar to IllegalArgumentException—bad caller input, impossible state after internal bug.
public class BadRowException extends Exception {
    public BadRowException(String message) {
        super(message);
    }

    public BadRowException(String message, Throwable cause) {
        super(message, cause);
    }
}

2. Naming and granularity

Prefer MalformedLedgerException over Exception1. Too many types overwhelm callers; too few force brittle instanceof chains. Aim for one type per recovery strategy.


3. Documenting intent in messages

Include row numbers, field names, and offending snippets (short) so logs are actionable:

throw new BadRowException("line " + lineNo + ": expected 3 columns, got " + parts.length);

4. Wrapping low-level causes

When Integer.parseInt fails inside your parser, wrap:

} catch (NumberFormatException ex) {
    throw new BadRowException("quantity not int at line " + lineNo, ex);
}

Callers that catch BadRowException can still call getCause() to inspect the original NumberFormatException.


5. Avoid controlling flow with exceptions

Exceptions are for exceptional paths—do not use them for ordinary loop termination; JVM and readers both suffer.


6. Serialization note (awareness only)

Long ago, many custom exceptions implemented Serializable with a serialVersionUID for RMI-era stacks. Modern services rarely rely on that—know the keyword exists, defer details until a serialization course.


7. Pitfalls

  • Swallowing your new checked exception in an empty catch.
  • Declaring throws Exception on public APIs—too broad; declare specific types.
  • Extending Throwable directly—almost always wrong; stick to Exception subtree.

Examples

Example A — checked domain exception

public class InsufficientStockException extends Exception {
    private final String sku;
    private final int requested;
    private final int available;

    public InsufficientStockException(String sku, int requested, int available) {
        super("sku " + sku + ": need " + requested + " have " + available);
        this.sku = sku;
        this.requested = requested;
        this.available = available;
    }

    public String sku() {
        return sku;
    }

    public int shortfall() {
        return requested - available;
    }
}

Callers that can reorder stock catch InsufficientStockException specifically; others propagate with throws.


Example B — unchecked invariant breach

public class InvariantBrokenException extends RuntimeException {
    public InvariantBrokenException(String message) {
        super(message);
    }
}

Use when an object’s internal state should be impossible if callers respected earlier contracts—similar spirit to IllegalStateException but domain-named.

Practice check — custom exception types

Multiple-choice check from the Matheion app lesson. Pick an answer for each question, then submit.

  1. 1.Extending `Exception` (not `RuntimeException`) creates…

  2. 2.Extending `RuntimeException` creates…

  3. 3.Exception chaining (`cause`) is useful to…

  4. 4.When are checked exceptions a reasonable design?

  5. 5.A custom exception class should usually…