Java Programs›Exceptions›Custom Exception

Custom Exception in Java

intermediate·  Exceptions  ·  Error Handling

Problem

A custom exception is a user-defined class that extends Exception (or RuntimeException) to represent a specific error condition in your own code.

Define a custom exception and throw it when a withdrawal exceeds the account balance.

Input
balance=100, withdraw=150
Output
Error: Insufficient funds for this withdrawal

Java Program

Java
class InsufficientFundsException extends Exception { InsufficientFundsException(String message) { super(message); // forwards the message so getMessage() can return it } } public class BankAccount { // throws clause forces callers to catch or declare this checked exception static void withdraw(double balance, double amount) throws InsufficientFundsException { if (amount > balance) { throw new InsufficientFundsException("Insufficient funds for this withdrawal"); } System.out.println("Withdrawal successful"); } public static void main(String[] args) { try { withdraw(100.0, 150.0); } catch (InsufficientFundsException e) { System.out.println("Error: " + e.getMessage()); } } }

Output

Error: Insufficient funds for this withdrawal

Core Logic

Sometimes the built-in exceptions don't fit — here a custom checked exception is thrown when a withdrawal breaks the balance rule.

How It Works
  1. 1class InsufficientFundsException extends Exception creates a new checked exception type.
  2. 2Its constructor forwards the message to super(message), so getMessage() returns it later.
  3. 3withdraw() declares throws InsufficientFundsException, requiring callers to handle it.
  4. 4When amount > balance, the method does throw new InsufficientFundsException(...) instead of continuing.
  5. 5The caller wraps the call in try/catch and reads the message back with e.getMessage().
Withdrawing 150 from a balance of 100 throws the custom exception, caught and printed as "Error: Insufficient funds for this withdrawal".
💡

Key Point: Extending Exception (not RuntimeException) makes this a checked exception — the compiler forces every caller to either catch it or declare it.

Key Concepts

custom exceptionextends Exceptionthrows

Approach 2: Unchecked Custom Exception

Java
class InsufficientFundsRuntimeException extends RuntimeException { InsufficientFundsRuntimeException(String message) { super(message); } } public class BankAccountUnchecked { // No throws clause needed — RuntimeException is unchecked static void withdraw(double balance, double amount) { if (amount > balance) { throw new InsufficientFundsRuntimeException("Insufficient funds for this withdrawal"); } System.out.println("Withdrawal successful"); } public static void main(String[] args) { try { withdraw(100.0, 150.0); } catch (InsufficientFundsRuntimeException e) { System.out.println("Error: " + e.getMessage()); } } }

Output

Error: Insufficient funds for this withdrawal

Core Logic

The same idea works as an unchecked exception too — extend RuntimeException instead, and callers are no longer forced to handle it.

How It Works
  1. 1class InsufficientFundsRuntimeException extends RuntimeException makes this an unchecked exception.
  2. 2withdraw() no longer needs a throws clause — the compiler doesn't require callers to catch or declare unchecked exceptions.
  3. 3The throw statement inside withdraw() is otherwise identical to the checked version.
  4. 4The caller still chooses to wrap the call in try/catch here, but it's now optional rather than compiler-enforced.
Calling withdraw(100.0, 150.0) throws the same way as before, but the compiler would have allowed calling it with no try/catch at all.
💡

Key Point: Use a checked exception when callers can reasonably be expected to recover (like retrying with a smaller amount); use an unchecked one when the failure represents a programming error or a business rule violation the caller isn't expected to routinely handle.

Key Concepts

RuntimeExceptionunchecked exceptionchecked vs. unchecked

Approach 3: Java 8

Java
import java.util.Optional; class InsufficientFundsException extends Exception { InsufficientFundsException(String message) { super(message); } } public class BankAccountOptional { static void withdraw(double balance, double amount) throws InsufficientFundsException { // filter() keeps the amount only if it fits the balance; orElseThrow() supplies the exception otherwise Optional.of(amount) .filter(amt -> amt <= balance) .orElseThrow(() -> new InsufficientFundsException("Insufficient funds for this withdrawal")); System.out.println("Withdrawal successful"); } public static void main(String[] args) { try { withdraw(100.0, 150.0); } catch (InsufficientFundsException e) { System.out.println("Error: " + e.getMessage()); } } }

Output

Error: Insufficient funds for this withdrawal

Core Logic

Optional.orElseThrow() can replace the explicit if-check-and-throw — filter the amount against the balance, and let the lambda supply the exception only if it fails.

How It Works
  1. 1Optional.of(amount) wraps the withdrawal amount in an Optional.
  2. 2.filter(amt -> amt <= balance) keeps the value only if it doesn't exceed the balance, becoming empty otherwise — the same condition the if (amount > balance) check tested.
  3. 3.orElseThrow(() -> new InsufficientFundsException(...)) returns the amount if present, or invokes the lambda to build and throw the custom exception if the Optional is empty.
  4. 4withdraw() still declares throws InsufficientFundsException, since orElseThrow() only defers when the exception is constructed, not whether it's checked.
Calling withdraw(100.0, 150.0) filters out 150.0 since it exceeds the balance, so orElseThrow() invokes the lambda and throws the custom exception, caught and printed the same way.
💡

Key Point: orElseThrow(Supplier) only builds the exception object when it's actually needed — a small efficiency edge over always constructing it up front, though the real draw here is expressing 'validate, then throw' as one chained expression.

Key Concepts

OptionalorElseThrow()lambda expression

Interview Questions

Should a custom exception extend Exception or RuntimeException?

Extend Exception when callers are expected to handle the problem, such as insufficient funds. Extend RuntimeException for programming errors or situations the caller usually can't fix, such as an invalid argument.

Why pass the message to super(message)?

The Throwable constructor stores it, so getMessage() and the printed stack trace show it. Many custom exceptions also accept a cause parameter to keep the original exception chained.

Learn the Concept

Related Programs