class InvalidAgeException extends Exception {
public InvalidAgeException(String message) {
super(message);
}
}Java Custom Exceptions: Creating Application-Specific Exception Classes
Direct Answer & Core Concept
Java provides many built-in exceptions such as IOException, NullPointerException, and IllegalArgumentException. However, real applications often have business rules and application-specific failures that are easier to understand with custom exception classes.
A custom exception is a class created by the developer to represent a specific error condition in an application. For example, an online banking application might need InsufficientBalanceException, while an e-commerce application might use OutOfStockException.
Custom exceptions make Java applications easier to read, maintain, debug, and extend.
Detailed Explanation & Mechanics
What Is a Custom Exception in Java?
A custom exception is a user-defined class that extends an existing exception class.
The simplest example is:
Here, InvalidAgeException is a custom checked exception because it extends Exception.
You can then use it like this:
throw new InvalidAgeException("Age must be 18 or above");The exception name itself explains what went wrong.
Why Create Custom Exceptions?
Built-in exceptions are useful for general programming errors, but they do not always describe business-specific problems.
For example:
throw new IllegalArgumentException("Invalid value");This tells us that an argument is invalid, but it does not explain the application's specific business rule.
A custom exception can be more descriptive:
throw new InsufficientBalanceException( "Account balance is too low");The exception type communicates additional meaning.
Common Examples of Custom Exceptions
Different applications can define different exception types.
Banking application
InsufficientBalanceExceptionAccountLockedExceptionInvalidTransactionExceptionE-commerce application
ProductOutOfStockExceptionInvalidCouponExceptionOrderNotFoundExceptionAuthentication system
InvalidCredentialsExceptionAccountDisabledExceptionSessionExpiredExceptionPayment system
PaymentFailedExceptionPaymentTimeoutExceptionInvalidPaymentMethodExceptionThe exception names should describe meaningful application conditions.
Creating a Custom Checked Exception
A checked custom exception normally extends Exception.
class InvalidAgeException extends Exception {
public InvalidAgeException(String message) {
super(message);
}
}The constructor passes the message to the parent Exception class using:
super(message);Now the exception can be thrown:
public void register(int age) throws InvalidAgeException {
if (age < 18) {
throw new InvalidAgeException( "User must be at least 18 years old");
}
}Because InvalidAgeException extends Exception, it is a checked exception.
Handling a Custom Checked Exception
The calling code can catch the exception:
public static void main(String[] args) {
try {
register(16);
} catch (InvalidAgeException e) {
System.out.println(e.getMessage());
}
}Output:
User must be at least 18 years oldThe application now has a meaningful exception type for the registration rule.
Custom Exception Class Structure
A typical custom exception has three important parts:
class InvalidAgeException extends Exception {
public InvalidAgeException(String message) {
super(message);
}
}Part 1: Class name
InvalidAgeExceptionThe name describes the problem.
Part 2: Parent exception
extends ExceptionThis determines that the exception is checked.
Part 3: Constructor
public InvalidAgeException(String message) {
super(message);
}The constructor allows the application to provide an error message.
Custom Unchecked Exceptions
A custom exception can also be unchecked.
To create an unchecked custom exception, extend RuntimeException.
class InvalidCouponException extends RuntimeException {
public InvalidCouponException(String message) {
super(message);
}
}You can throw it without declaring it with throws:
public void applyCoupon(String coupon) {
if (coupon == null || coupon.isBlank()) {
throw new InvalidCouponException( "Coupon code cannot be empty");
}
}The caller is not required by the compiler to catch or declare this exception.
Checked vs Unchecked Custom Exceptions
The parent class determines whether your custom exception is checked or unchecked.
| Custom exception | Parent class | Type |
|---|---|---|
InvalidAgeException | Exception | Checked |
PaymentException | Exception | Checked |
InvalidCouponException | RuntimeException | Unchecked |
OrderStateException | RuntimeException | Unchecked |
Checked exception
class PaymentException extends Exception {
}Unchecked exception
class PaymentException extends RuntimeException {
}Choosing between the two should be based on the application's exception-handling design rather than simply following a fixed rule.
Custom Exception With a Default Constructor
A custom exception can have a no-argument constructor.
class AccountLockedException extends Exception {
public AccountLockedException() {
super("Account is locked");
}
}Now you can write:
throw new AccountLockedException();The default message is defined inside the exception class.
Custom Exception With a Message
A more flexible design accepts a message:
class AccountLockedException extends Exception {
public AccountLockedException(String message) {
super(message);
}
}Then:
throw new AccountLockedException( "Account locked after multiple failed login attempts");This allows different parts of the application to provide different messages when appropriate.
Custom Exception With a Cause
Exceptions can also preserve the original cause.
class PaymentException extends Exception {
public PaymentException( String message, Throwable cause) {
super(message, cause);
}
}You can use it like this:
try {
processPayment();
} catch (SQLException e) {
throw new PaymentException( "Payment processing failed", e);
}The original SQLException remains available as the cause.
This is useful when converting a low-level technical exception into an application-level exception.
Custom Exception With Multiple Constructors
A reusable exception class can provide several constructors.
class OrderException extends Exception {
public OrderException() {
super();
} public OrderException(String message) {
super(message);
} public OrderException( String message, Throwable cause) {
super(message, cause);
}
}Now developers can create the exception in different ways.
throw new OrderException();Or:
throw new OrderException("Order could not be processed");Or:
throw new OrderException( "Database operation failed", databaseException);Custom Exceptions for Business Rules
One of the most useful applications of custom exceptions is representing business rules.
Consider a banking application.
class InsufficientBalanceException extends Exception {
public InsufficientBalanceException( String message) {
super(message);
}
}The withdrawal method can use it:
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException( "Insufficient account balance");
} balance -= amount;
}The exception describes the actual business condition instead of exposing an unrelated technical exception.
Complete Banking Example
Here is a complete example:
class InsufficientBalanceException extends Exception {
public InsufficientBalanceException( String message) {
super(message);
}
}Bank account:
class BankAccount {
private double balance = 5000;
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException( "Insufficient balance. Available: " + balance);
} balance -= amount;
System.out.println( "Withdrawal successful");
}
}Using the class:
public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount();
try {
account.withdraw(7000);
} catch (InsufficientBalanceException e) {
System.out.println(e.getMessage());
}
}
}Possible output:
Insufficient balance. Available: 5000.0Custom Exceptions in E-Commerce
Suppose an online store needs to prevent customers from ordering unavailable products.
Create:
class ProductOutOfStockException extends Exception {
public ProductOutOfStockException( String message) {
super(message);
}
}Then:
public void placeOrder(int quantity) throws ProductOutOfStockException {
int availableStock = 3;
if (quantity > availableStock) {
throw new ProductOutOfStockException( "Only " + availableStock + " products are available");
} System.out.println("Order placed");
}The exception directly represents the business problem.
Custom Exceptions in Authentication
An authentication system might need to distinguish between different failures.
For example:
class InvalidCredentialsException extends RuntimeException {
public InvalidCredentialsException( String message) {
super(message);
}
}Usage:
public void login( String username, String password) {
if (!isValid(username, password)) {
throw new InvalidCredentialsException( "Username or password is incorrect");
}
}This gives the application a meaningful exception type that can be handled by an appropriate application layer.
Custom Exception Hierarchies
Large applications may have multiple related custom exceptions.
For example:
ApplicationException | +-- PaymentException | | | +-- PaymentTimeoutException | +-- PaymentFailedException | +-- OrderException | +-- OrderNotFoundException +-- OrderCancelledExceptionYou can create a common base exception:
class ApplicationException extends RuntimeException {
public ApplicationException(String message) {
super(message);
}
}Then:
class PaymentException extends ApplicationException {
public PaymentException(String message) {
super(message);
}
}And:
class PaymentTimeoutException extends PaymentException {
public PaymentTimeoutException(String message) {
super(message);
}
}This hierarchy allows developers to catch either a specific exception or a broader application exception.
Catching a Parent Custom Exception
Suppose:
class PaymentException extends RuntimeException {
}And:
class PaymentTimeoutException extends PaymentException {
}You can catch the specific exception:
try {
processPayment();
} catch (PaymentTimeoutException e) {
System.out.println("Payment timed out");
}Or catch the broader type:
try {
processPayment();
} catch (PaymentException e) {
System.out.println("Payment failed");
}This is useful when an application wants different levels of error handling.
Custom Exception Packages
In larger Java applications, custom exceptions are commonly placed in a dedicated package.
For example:
src/
└── com/example/app/
├── controller/
├── service/
├── repository/
├── model/
└── exception/
├── PaymentException.java
├── OrderException.java
└── UserNotFoundException.javaThis keeps exception classes organized and easy to find.
Example Package Declaration
A custom exception might be:
package com.example.app.exception;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}Another class can import it:
import com.example.app.exception.UserNotFoundException;Then use it:
throw new UserNotFoundException( "User with ID 101 was not found");Custom Exceptions and throw
Custom exceptions are commonly used together with the throw keyword.
Example:
if (balance < amount) {
throw new InsufficientBalanceException( "Insufficient balance");
}Here:
InsufficientBalanceExceptionis the custom exception class.newcreates an exception object.throwactually throws that object.
For a checked exception, the method may also need:
throws InsufficientBalanceExceptionSo all three concepts can appear together:
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException( "Insufficient balance");
}
}Custom Exceptions and throws
A checked custom exception generally requires a throws declaration if it is not handled inside the method.
class InvalidAgeException extends Exception {
public InvalidAgeException(String message) {
super(message);
}
}Then:
public void register(int age) throws InvalidAgeException {
if (age < 18) {
throw new InvalidAgeException( "Age must be at least 18");
}
}The relationship is:
Custom Exception↓throw↓Exception leaves method↓throws declares it↓Caller handles itShould Custom Exceptions Extend Exception or RuntimeException?
This is an important design decision.
Extend Exception
Use:
class MyException extends Exception {
}when you want a checked exception.
The caller must catch or declare it.
Extend RuntimeException
Use:
class MyException extends RuntimeException {
}when you want an unchecked exception.
The compiler does not force callers to catch or declare it.
There is no universal rule that every business exception must be checked or every validation exception must be unchecked. The choice should match the application's API and error-handling strategy.
Common Mistakes When Creating Custom Exceptions
Mistake 1: Creating Too Many Exceptions
Not every error needs its own class.
For example, creating separate exception classes for every small validation message can make an application unnecessarily complicated.
Create a custom exception when the exception type itself carries useful meaning.
Mistake 2: Using Generic Names
Avoid vague names such as:
MyExceptionCustomErrorSomethingWrongExceptionPrefer names that describe the condition:
UserNotFoundExceptionPaymentFailedExceptionInvalidOrderStateExceptionMistake 3: Losing the Original Cause
Avoid:
catch (SQLException e) {
throw new PaymentException( "Payment failed");
}If the original exception is useful for diagnosis, preserve it:
catch (SQLException e) {
throw new PaymentException( "Payment failed", e);
}Mistake 4: Exposing Sensitive Information
Exception messages should not expose passwords, authentication tokens, private keys, or other sensitive information.
Avoid messages such as:
Password abc123 is incorrectPrefer:
Invalid username or passwordMistake 5: Catching Everything
Avoid unnecessarily broad handling:
catch (Exception e) {
// ignore
}This can hide failures and make debugging difficult.
Catch exceptions at a level where the application can actually make a useful decision.
Best Practices for Custom Exceptions
1. Give the exception a meaningful name
Use:
OrderNotFoundExceptioninstead of:
OrderError2. Choose the correct parent class
Decide whether the exception should be checked or unchecked.
3. Provide useful constructors
A message constructor is commonly useful:
public MyException(String message) {
super(message);
}4. Preserve causes
When wrapping another exception:
super(message, cause);5. Keep exception classes focused
An exception class normally represents an error condition. Avoid putting unrelated business logic inside it.
6. Keep messages useful
Messages should help developers understand what happened without exposing sensitive data.
7. Organize exceptions consistently
A dedicated exception package can make larger applications easier to navigate.
Custom Exception Example With Cause
Here is a more complete example:
class UserServiceException extends RuntimeException {
public UserServiceException(String message) {
super(message);
} public UserServiceException( String message, Throwable cause) {
super(message, cause);
}
}Service code:
public User findUser(int id) {
try {
return database.findUser(id);
} catch (SQLException e) {
throw new UserServiceException( "Unable to load user", e);
}
}The service exposes an application-level exception while retaining the original database exception as the cause.
Custom Exceptions in REST Applications
Custom exceptions are also commonly used in web applications.
For example:
class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}Service:
public User getUser(long id) {
User user = repository.findById(id);
if (user == null) {
throw new UserNotFoundException( "User not found");
} return user;
}A web application's exception-handling layer can then convert the exception into an appropriate HTTP response.
For example, the application may map the condition to a 404 Not Found response.
The exact implementation depends on the framework being used.
Custom Exceptions vs Built-In Exceptions
Use a built-in exception when it already accurately describes the failure.
For example:
throw new IllegalArgumentException( "Age cannot be negative");There may be no need to create:
NegativeAgeExceptionBut if the application needs to distinguish the condition as a meaningful domain event, a custom exception may be appropriate.
For example:
throw new InsufficientBalanceException( "Insufficient balance");The exception type itself carries useful business meaning.
Custom Exception Design Example
A clean application might have:
exception/
├── ApplicationException.java
├── UserNotFoundException.java
├── PaymentException.java
├── PaymentTimeoutException.java
├── OrderException.java
└── InsufficientBalanceException.javaA possible hierarchy:
RuntimeException
│
└── ApplicationException
│
├── UserNotFoundException
├── OrderException
└── PaymentException
│
└── PaymentTimeoutExceptionThis can make exception handling more structured in larger applications.
Frequently Asked Questions
What is a custom exception in Java?
A custom exception is a developer-created exception class used to represent an application-specific or domain-specific error condition.
How do I create a custom exception?
Extend an existing exception class.
class MyException extends Exception {
}How do I create a checked custom exception?
Extend Exception:
class MyException extends Exception {
}How do I create an unchecked custom exception?
Extend RuntimeException:
class MyException extends RuntimeException {
}Why use custom exceptions?
They provide meaningful exception types for application-specific conditions and can make exception handling and debugging clearer.
Can a custom exception have a message?
Yes.
public MyException(String message) {
super(message);
}Can a custom exception preserve another exception?
Yes. Use a cause:
public MyException( String message, Throwable cause) {
super(message, cause);
}Can custom exceptions have multiple constructors?
Yes. You can provide constructors for messages, causes, or both.
Can custom exceptions be inherited?
Yes. Custom exception classes can form their own inheritance hierarchy.
Should every application error have a custom exception?
No. Use custom exceptions when a distinct exception type provides meaningful value. Built-in exceptions are often sufficient for common programming and validation errors.
Practice Questions
Beginner
What is a custom exception?
Which class can be extended to create a checked custom exception?
Which class can be extended to create an unchecked custom exception?
Why is
super(message)used in an exception constructor?How do you throw a custom exception?
Intermediate
What is the difference between a checked and unchecked custom exception?
Why should custom exception names describe the actual failure?
How can you preserve the original cause when wrapping an exception?
Can a custom exception have multiple constructors?
Why might an application create an
InsufficientBalanceException?
Advanced
How can custom exceptions form an inheritance hierarchy?
When should an application use a built-in exception instead of a custom exception?
How can custom exceptions be used across service and repository layers?
Why can excessive custom exceptions make an application harder to maintain?
How can a REST application convert a custom exception into an HTTP response?
Quick Revision
Custom Exception↓Create a class↓Extend Exception or RuntimeException↓Add useful constructors↓throw the exception when the condition occurs↓Handle it at the appropriate application layerChecked custom exception
class PaymentException extends Exception {
}Unchecked custom exception
class PaymentException extends RuntimeException {
}Throwing a custom exception
throw new PaymentException( "Payment failed");Declaring a checked custom exception
public void pay() throws PaymentException {
}The key idea is simple:
A custom exception gives your application a meaningful type for a specific failure condition.
Graphics and Design Ideas
Graphic 1: Custom Exception Creation
Existing Java Exception↓Extend the class↓Custom Exception Class↓throw when needed↓Handle the errorGraphic 2: Checked vs Unchecked
Custom Exception
│
┌──────────┴──────────┐
↓
↓
Exception RuntimeException
↓
↓
Checked UncheckedGraphic 3: Banking Example
Withdraw ₹7,000↓Balance = ₹5,000↓Amount > Balance↓InsufficientBalanceException↓Application handles errorGraphic 4: Exception Hierarchy
RuntimeException
│
└── ApplicationException
│
├── UserNotFoundException
├── OrderException
└── PaymentException
│
└── PaymentTimeoutExceptionGraphic 5: throw + throws + Custom Exception
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException( "Insufficient balance");
}
}Highlight the three concepts separately:
Custom exception → InsufficientBalanceException
throw → actually creates/throws the exception object
throws → declares the checked exception in the method signature
Runnable Code Example: Java Custom Exceptions
Java 17+public class CustomExceptionDemo {
// Custom domain exception
static class InsufficientBalanceException extends Exception {
private final double currentBalance;
private final double requestedAmount;
public InsufficientBalanceException(double balance, double amount) {
super("Cannot withdraw $" + amount + ": Available balance is $" + balance);
this.currentBalance = balance;
this.requestedAmount = amount;
}
} public static void withdraw(double balance, double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException(balance, amount);
} System.out.println("Withdrawal successful: $" + amount);
} public static void main(String[] args) {
try {
withdraw(100.0, 250.0);
} catch (InsufficientBalanceException e) {
System.err.println(e.getMessage());
}
}
}