Custom Exception in Java
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.
Java Program
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
Core Logic
Sometimes the built-in exceptions don't fit — here a custom checked exception is thrown when a withdrawal breaks the balance rule.
- 1
class InsufficientFundsException extends Exceptioncreates a new checked exception type. - 2Its constructor forwards the message to
super(message), sogetMessage()returns it later. - 3
withdraw()declaresthrows InsufficientFundsException, requiring callers to handle it. - 4When
amount > balance, the method doesthrow new InsufficientFundsException(...)instead of continuing. - 5The caller wraps the call in
try/catchand reads the message back withe.getMessage().
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
Approach 2: Unchecked Custom Exception
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
Core Logic
The same idea works as an unchecked exception too — extend RuntimeException instead, and callers are no longer forced to handle it.
- 1
class InsufficientFundsRuntimeException extends RuntimeExceptionmakes this an unchecked exception. - 2
withdraw()no longer needs athrowsclause — the compiler doesn't require callers to catch or declare unchecked exceptions. - 3The
throwstatement insidewithdraw()is otherwise identical to the checked version. - 4The caller still chooses to wrap the call in
try/catchhere, but it's now optional rather than compiler-enforced.
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
Approach 3: Java 8
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
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.
- 1
Optional.of(amount)wraps the withdrawal amount in anOptional. - 2
.filter(amt -> amt <= balance)keeps the value only if it doesn't exceed the balance, becoming empty otherwise — the same condition theif (amount > balance)check tested. - 3
.orElseThrow(() -> new InsufficientFundsException(...))returns the amount if present, or invokes the lambda to build and throw the custom exception if theOptionalis empty. - 4
withdraw()still declaresthrows InsufficientFundsException, sinceorElseThrow()only defers when the exception is constructed, not whether it's checked.
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
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.