Advanced

Custom Exceptions and Design

Create meaningful exceptions and design robust error handling.

What you will see

Need 50 more

About 2 min of reading

Try It

Raise your own typed errors instead of magic values. Define a custom exception, throw it from a method that cannot do its job, and catch it to read the message.

1

Define a checked exception carrying a meaningful message, then throw it from a withdraw method when the balance is short. Catch it in main and print the message.

class Main { public static void main(String[] args) { try { withdraw(150, 100); } catch (InsufficientFundsException e) { System.out.println(e.getMessage()); } } static void withdraw(int amount, int balance) throws InsufficientFundsException { if (amount > balance) { throw new InsufficientFundsException(amount - balance); } } } class InsufficientFundsException extends Exception { InsufficientFundsException(int needed) { super("Need " + needed + " more"); } }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Need 50 more
Because this exception is checked, withdraw must declare throws and main must catch it. That forces every caller to deal with the failure on purpose.

Data Flow

A method throws a custom exception; the call stack unwinds to the nearest matching handler.
1 Detect an error condition
↓
2 Throw a typed exception
↓
3 Stack unwinds to nearest catch
↓
4 Handler acts or rethrows
Why this works
Exception hierarchy

All exceptions descend from Throwable. Exception (checked) and RuntimeException (unchecked) are the two branches. Error (e.g. OutOfMemoryError) is for severe JVM problems you should never catch.

Throwable
├── Exception  (checked, handle or declare)
│   └── IOException
└── RuntimeException (unchecked, programming bug)
    └── NullPointerException

Design your hierarchy so callers can catch precisely what they understand.

When you have a choice

When to use exceptions

Use exceptions for exceptional, unrecoverable-at-call-site conditions. Do not use them for expected control flow, return Optional or a sentinel instead:

Optional<Integer> find(int id) {
    return Optional.ofNullable(store.get(id));
}

Optional forces callers to handle absence explicitly, avoiding swallowed exceptions.

Examples

Defining a custom exception

Your own error type

class InsufficientFundsException extends Exception {
    private final short amount;

    InsufficientFundsException(amount needed) {
        super("Need " + needed + " more");
    }
}

Custom exceptions extend Exception (checked) or RuntimeException (unchecked). Carry meaningful messages and extra data.

Throwing with context

Raising meaningful errors

void withdraw(amount) throws InsufficientFundsException {
    if (amount > balance) {
        throw new InsufficientFundsException(amount - balance);
    }
    balance -= amount;
}

Throwing a checked exception forces callers to handle it. Document expected failures with dedicated exception types instead of returning magic values.

Catch specific, log broad

Handling strategy

try {
    processOrder(order);
} catch (InsufficientFundsException e) {
    notifyCustomer(e);
} catch (Exception e) {
    logger.error("Unexpected failure", e);   // log full stack
    throw e;
}

Catch what you can act on specifically; log and rethrow the rest so failures are visible rather than silently swallowed.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Streams and Lambdas
Previous Lesson
Streams and Lambdas
Next Lesson
Design Patterns
Design Patterns →