Back to Exception Hierarchy
Q1Question 1 of 2

What are custom exceptions and how do you create them?

Key Concepts
  • Extend Exception for a checked custom exception (caller must handle)
  • Extend RuntimeException for an unchecked custom exception (caller is not forced)
  • Include a descriptive message, the cause (original exception), and relevant business data
  • Name the exception after the business problem: InsufficientFundsException, AccountNotFoundException
  • Use @ResponseStatus in Spring to map custom exceptions to HTTP status codes

Definition

Create a custom exception by extending Exception (checked) or RuntimeException (unchecked). Include a message, cause, and relevant business data.

Custom exception best practices:

  • Extend the right parent: Exception for recoverable problems (checked). RuntimeException for programming errors (unchecked).
  • Descriptive name: InsufficientFundsException, not MyException.
  • Include context: Account ID, attempted amount, current balance.
  • Preserve the cause: Pass the original exception to super(message, cause).

A generic bank error slip just says "error." A custom exception slip says: "InsufficientFundsException: Account SAV2001 attempted to debit 100,000 but balance is only 50,000." The second one tells you exactly what went wrong.

Understanding the Concepts

How do you create a custom checked exception?

JAVA
public class InsufficientFundsException extends Exception {
    private final String accountId;
    private final BigDecimal attempted;
    private final BigDecimal available;

    public InsufficientFundsException(String accountId, BigDecimal attempted, BigDecimal available) {
        super(String.format("Account %s: attempted %s but only %s available",
                accountId, attempted, available));
        this.accountId = accountId;
        this.attempted = attempted;
        this.available = available;
    }

    public String getAccountId() { return accountId; }
    public BigDecimal getAttempted() { return attempted; }
    public BigDecimal getAvailable() { return available; }
}

How do you create a custom unchecked exception?

JAVA
public class AccountNotFoundException extends RuntimeException {
    private final String accountId;

    public AccountNotFoundException(String accountId) {
        super("Account not found: " + accountId);
        this.accountId = accountId;
    }

    public AccountNotFoundException(String accountId, Throwable cause) {
        super("Account not found: " + accountId, cause);  // preserve the cause
        this.accountId = accountId;
    }

    public String getAccountId() { return accountId; }
}

How do you use them?

JAVA
public class TransferService {
    public void transfer(String fromId, String toId, BigDecimal amount)
            throws InsufficientFundsException {  // checked: caller must handle

        Account from = repository.findById(fromId)
            .orElseThrow(() -> new AccountNotFoundException(fromId));  // unchecked: no throws needed

        if (from.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException(fromId, amount, from.getBalance());
        }
        // proceed with transfer
    }
}

When should you use checked vs unchecked custom exceptions?

DIAGRAM
- **Checked:** Caller can meaningfully recover. `InsufficientFundsException` → show user a message, suggest a smaller amount.
- **Unchecked:** Programming error or unrecoverable situation. `AccountNotFoundException` → should never happen in normal flow, indicates a bug or data inconsistency.

What about Spring @ResponseStatus?

JAVA
@ResponseStatus(HttpStatus.NOT_FOUND)
public class AccountNotFoundException extends RuntimeException {
    // Spring automatically returns 404 when this is thrown from a controller
}

@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public class InsufficientFundsException extends RuntimeException {
    // Spring returns 422
}

What are common mistakes?

  • Not including the cause. throw new CustomException("fail") loses the stack trace of the original error. Use throw new CustomException("fail", originalException).
  • Using generic names. MyException, AppException tell nothing. Name it after the business problem.
  • Making every custom exception checked. Most modern frameworks (Spring, Jakarta EE) prefer unchecked exceptions.

What should a senior engineer know?

  • Exception hierarchy for a project: Create a base BankException extends RuntimeException, then InsufficientFundsException extends BankException. Catch BankException at the boundary.
  • @ControllerAdvice + @ExceptionHandler in Spring provides centralized exception-to-HTTP mapping.
  • serialVersionUID: If your exceptions are serialized (remote calls), add private static final long serialVersionUID = 1L;.

Visual Overview

DIAGRAM
  Custom exception hierarchy for banking:

  RuntimeException
    └── BankException (base for all bank exceptions)
          ├── AccountNotFoundException
          ├── InsufficientFundsException
          ├── DuplicateTransactionException
          └── InvalidAmountException

  Each includes: message, business data (accountId, amount), cause
  Spring: @ResponseStatus maps to HTTP codes

Key Takeaway

Create custom exceptions by extending Exception (checked, caller must handle) or RuntimeException (unchecked, caller is not forced). Name them after the business problem, include relevant context (account ID, amounts), and always preserve the original cause.

In banking, InsufficientFundsException tells you which account, what amount was attempted, and what was available. AccountNotFoundException tells you which account ID was not found. These are far more useful than generic Exception or a string error code.

One sentence to remember: extend Exception for recoverable problems, RuntimeException for bugs, include the message, the cause, and the business data.

---

Share
Use ← → arrow keys to navigate