Custom Exceptions in Java

Naveen AnamaneniUpdated · 5 min read

IllegalArgumentException says "something was wrong with an argument". InsufficientFundsException says exactly what went wrong in a bank, and callers can catch that problem specifically, without catching unrelated ones by accident. A custom exception is simply a class that extends Exception (for a checked exception) or RuntimeException (for an unchecked one). Writing a good one takes a few lines and makes your code's failures self-explanatory.

A Minimal Custom Exception

Bank.java▶ Try it
public class Bank {
    static class InsufficientFundsException extends Exception {
        private final long shortBy;
 
        InsufficientFundsException(long shortBy) {
            super("Short by Rs. " + shortBy);
            this.shortBy = shortBy;
        }
 
        long shortBy() { return shortBy; }
    }
 
    static long balance = 1500;
 
    static void withdraw(long amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException(amount - balance);
        }
        balance -= amount;
    }
 
    public static void main(String[] args) {
        try {
            withdraw(2000);
        } catch (InsufficientFundsException e) {
            System.out.println(e.getMessage());
            System.out.println("Ask Srihitha to add Rs. " + e.shortBy());
        }
    }
}
Output
Short by Rs. 500
Ask Srihitha to add Rs. 500

How it works

  1. extends Exception makes it a checked exception, so withdraw must declare it.
  2. super("Short by Rs. " + shortBy) sets the message that getMessage() returns.
  3. The extra field shortBy carries useful data, so the caller can act on it without parsing the message.

Checked or Unchecked?

Make it checked (extends Exception) when...Make it unchecked (extends RuntimeException) when...
The caller can reasonably recover: not enough money, file not found, user not registeredIt signals a bug or broken rule the caller can't fix at runtime
You want the compiler to remind every callerForcing a try everywhere would just add noise

Many modern codebases lean toward unchecked custom exceptions and document them; both styles are valid when used consistently.

The Constructors to Provide

A well-behaved exception offers the same constructors as Java's own, so it can be created with or without a message and a cause:

Constructors
public class PaymentException extends Exception {
    public PaymentException(String message) {
        super(message);
    }
 
    public PaymentException(String message, Throwable cause) {
        super(message, cause);
    }
}

The (message, cause) constructor is the important one: it lets you wrap a lower-level exception without losing it (see "Chained Exceptions in Java").

Putting It Together: A Number-Parsing Utility

This chapter's checkpoint combines a custom checked exception, a multi-catch and a chained cause:

NumberTools.java▶ Try it
public class NumberTools {
    static class InvalidAmountException extends Exception {
        InvalidAmountException(String input, Throwable cause) {
            super("Not a valid amount: " + input, cause);
        }
    }
 
    static long parseAmount(String text) throws InvalidAmountException {
        long value;
        try {
            value = Long.parseLong(text.strip().replace(",", ""));
        } catch (NumberFormatException | NullPointerException e) {
            throw new InvalidAmountException(String.valueOf(text), e);
        }
        if (value < 0) {
            throw new InvalidAmountException(text, null);
        }
        return value;
    }
 
    public static void main(String[] args) {
        String[] inputs = {"1,200", " 450 ", "12a", null, "-5"};
        for (String in : inputs) {
            try {
                System.out.println(in + " -> " + parseAmount(in));
            } catch (InvalidAmountException e) {
                Throwable cause = e.getCause();
                String why = cause == null
                    ? "none" : cause.getClass().getSimpleName();
                System.out.println(e.getMessage() + " (cause: " + why + ")");
            }
        }
    }
}
Output
1,200 -> 1200
 450  -> 450
Not a valid amount: 12a (cause: NumberFormatException)
Not a valid amount: null (cause: NullPointerException)
Not a valid amount: -5 (cause: none)

How it works

  1. strip() and replace(",", "") clean the input, so "1,200" and " 450 " parse correctly.
  2. Multi-catch handles both ways parsing can fail: bad text (NumberFormatException) and no text (NullPointerException from calling strip() on null).
  3. Each failure is wrapped in InvalidAmountException with the original as its cause.
  4. A negative number parses fine but breaks a rule, so it's thrown with no cause.
  5. Callers deal with one meaningful exception type and can still inspect the cause.

Common Mistakes

Extending Throwable or Error. Extend Exception or RuntimeException.

No message or cause constructors. Callers can't explain or wrap failures properly.

Catching your custom exception and throwing a new one without the cause. Pass it along as the cause.

Interview Corner

How do you create a custom exception in Java?

Define a class that extends Exception for a checked exception or RuntimeException for an unchecked one, and provide constructors that pass a message, and optionally a cause, to super. Add fields for any extra data callers need. Name it after the problem, ending in "Exception", and throw it with throw new MyException(...).

Should a custom exception be checked or unchecked?

Make it checked if callers can reasonably recover and should be forced to consider the case, such as insufficient funds. Make it unchecked if it represents a programming error or a condition callers usually can't handle at that point, to avoid cluttering every call site with try-catch.

Practice

  1. Easy: Create an unchecked InvalidMarksException and throw it from setMarks(int marks) for values outside 0 to 100.
  2. Harder: Extend NumberTools with parsePercent(String text) that reuses parseAmount and throws InvalidAmountException for values above 100, with no cause.

Quick check: Custom exceptions

4 questions
1What should a custom checked exception extend?
2How does a custom exception set its message?
3Why add a (message, cause) constructor?
4When is a standard exception better than a custom one?

Key Takeaways

Was this tutorial helpful?

Lesson 233 of 482