Topic 84 of 119 8. Exception Handling in Java Interactive Tutorial

Java Custom Exceptions: Creating Application-Specific Exception Classes

9 mins read Last updated: 22 Aug 2026 29,532 views

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:

Main.java
Run Online
class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

Here, InvalidAgeException is a custom checked exception because it extends Exception.

You can then use it like this:

Main.java
Run Online
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:

Main.java
Run Online
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:

Main.java
Run Online
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

Main.java
Run Online
InsufficientBalanceExceptionAccountLockedExceptionInvalidTransactionException

E-commerce application

Main.java
Run Online
ProductOutOfStockExceptionInvalidCouponExceptionOrderNotFoundException

Authentication system

Main.java
Run Online
InvalidCredentialsExceptionAccountDisabledExceptionSessionExpiredException

Payment system

Main.java
Run Online
PaymentFailedExceptionPaymentTimeoutExceptionInvalidPaymentMethodException

The exception names should describe meaningful application conditions.


Creating a Custom Checked Exception

A checked custom exception normally extends Exception.

Main.java
Run Online
class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

The constructor passes the message to the parent Exception class using:

Now the exception can be thrown:

Main.java
Run Online
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:

Main.java
Run Online
public static void main(String[] args) {
    try {
        register(16);
    } catch (InvalidAgeException e) {
        System.out.println(e.getMessage());
    }
}

Output:

Main.java
Run Online
User must be at least 18 years old

The application now has a meaningful exception type for the registration rule.


Custom Exception Class Structure

A typical custom exception has three important parts:

Main.java
Run Online
class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

Part 1: Class name

The name describes the problem.

Part 2: Parent exception

This determines that the exception is checked.

Part 3: Constructor

Main.java
Run Online
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.

Main.java
Run Online
class InvalidCouponException extends RuntimeException {
    public InvalidCouponException(String message) {
        super(message);
    }
}

You can throw it without declaring it with throws:

Main.java
Run Online
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 exceptionParent classType
InvalidAgeExceptionExceptionChecked
PaymentExceptionExceptionChecked
InvalidCouponExceptionRuntimeExceptionUnchecked
OrderStateExceptionRuntimeExceptionUnchecked

Checked exception

Main.java
Run Online
class PaymentException extends Exception {
}

Unchecked exception

Main.java
Run Online
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.

Main.java
Run Online
class AccountLockedException extends Exception {
    public AccountLockedException() {
        super("Account is locked");
    }
}

Now you can write:

Main.java
Run Online
throw new AccountLockedException();

The default message is defined inside the exception class.


Custom Exception With a Message

A more flexible design accepts a message:

Main.java
Run Online
class AccountLockedException extends Exception {
    public AccountLockedException(String message) {
        super(message);
    }
}

Then:

Main.java
Run Online
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.

Main.java
Run Online
class PaymentException extends Exception {
    public PaymentException( String message, Throwable cause) {
        super(message, cause);
    }
}

You can use it like this:

Main.java
Run Online
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.

Main.java
Run Online
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.

Main.java
Run Online
throw new OrderException();

Or:

Main.java
Run Online
throw new OrderException("Order could not be processed");

Or:

Main.java
Run Online
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.

Main.java
Run Online
class InsufficientBalanceException extends Exception {
    public InsufficientBalanceException( String message) {
        super(message);
    }
}

The withdrawal method can use it:

Main.java
Run Online
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:

Main.java
Run Online
class InsufficientBalanceException extends Exception {
    public InsufficientBalanceException( String message) {
        super(message);
    }
}

Bank account:

Main.java
Run Online
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:

Main.java
Run Online
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:

Main.java
Run Online
Insufficient balance. Available: 5000.0

Custom Exceptions in E-Commerce

Suppose an online store needs to prevent customers from ordering unavailable products.

Create:

Main.java
Run Online
class ProductOutOfStockException extends Exception {
    public ProductOutOfStockException( String message) {
        super(message);
    }
}

Then:

Main.java
Run Online
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:

Main.java
Run Online
class InvalidCredentialsException extends RuntimeException {
    public InvalidCredentialsException( String message) {
        super(message);
    }
}

Usage:

Main.java
Run Online
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:

Main.java
Run Online
ApplicationException | +-- PaymentException | | | +-- PaymentTimeoutException | +-- PaymentFailedException | +-- OrderException | +-- OrderNotFoundException +-- OrderCancelledException

You can create a common base exception:

Main.java
Run Online
class ApplicationException extends RuntimeException {
    public ApplicationException(String message) {
        super(message);
    }
}

Then:

Main.java
Run Online
class PaymentException extends ApplicationException {
    public PaymentException(String message) {
        super(message);
    }
}

And:

Main.java
Run Online
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:

Main.java
Run Online
class PaymentException extends RuntimeException {
}

And:

Main.java
Run Online
class PaymentTimeoutException extends PaymentException {
}

You can catch the specific exception:

Main.java
Run Online
try {
    processPayment();
} catch (PaymentTimeoutException e) {
    System.out.println("Payment timed out");
}

Or catch the broader type:

Main.java
Run Online
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:

Architecture Diagram
src/
└── com/example/app/
    ├── controller/
    ├── service/
    ├── repository/
    ├── model/
    └── exception/
        ├── PaymentException.java
        ├── OrderException.java
        └── UserNotFoundException.java

This keeps exception classes organized and easy to find.


Example Package Declaration

A custom exception might be:

Main.java
Run Online
package com.example.app.exception;
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

Another class can import it:

Main.java
Run Online
import com.example.app.exception.UserNotFoundException;

Then use it:

Main.java
Run Online
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:

Main.java
Run Online
if (balance < amount) {
    throw new InsufficientBalanceException( "Insufficient balance");
}

Here:

  • InsufficientBalanceException is the custom exception class.

  • new creates an exception object.

  • throw actually throws that object.

For a checked exception, the method may also need:

Main.java
Run Online
throws InsufficientBalanceException

So all three concepts can appear together:

Main.java
Run Online
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.

Main.java
Run Online
class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

Then:

Main.java
Run Online
public void register(int age) throws InvalidAgeException {
    if (age < 18) {
        throw new InvalidAgeException( "Age must be at least 18");
    }
}

The relationship is:

Main.java
Run Online
Custom Exception↓throw↓Exception leaves method↓throws declares it↓Caller handles it

Should Custom Exceptions Extend Exception or RuntimeException?

This is an important design decision.

Extend Exception

Use:

Main.java
Run Online
class MyException extends Exception {
}

when you want a checked exception.

The caller must catch or declare it.

Extend RuntimeException

Use:

Main.java
Run Online
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:

Main.java
Run Online
MyExceptionCustomErrorSomethingWrongException

Prefer names that describe the condition:

Main.java
Run Online
UserNotFoundExceptionPaymentFailedExceptionInvalidOrderStateException

Mistake 3: Losing the Original Cause

Avoid:

Main.java
Run Online
catch (SQLException e) {
    throw new PaymentException( "Payment failed");
}

If the original exception is useful for diagnosis, preserve it:

Main.java
Run Online
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:

Main.java
Run Online
Password abc123 is incorrect

Prefer:

Main.java
Run Online
Invalid username or password

Mistake 5: Catching Everything

Avoid unnecessarily broad handling:

Main.java
Run Online
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:

instead of:

2. Choose the correct parent class

Decide whether the exception should be checked or unchecked.

3. Provide useful constructors

A message constructor is commonly useful:

Main.java
Run Online
public MyException(String message) {
    super(message);
}

4. Preserve causes

When wrapping another exception:

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:

Main.java
Run Online
class UserServiceException extends RuntimeException {
    public UserServiceException(String message) {
        super(message);
    } public UserServiceException( String message, Throwable cause) {
        super(message, cause);
    }
}

Service code:

Main.java
Run Online
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:

Main.java
Run Online
class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

Service:

Main.java
Run Online
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:

Main.java
Run Online
throw new IllegalArgumentException( "Age cannot be negative");

There may be no need to create:

But if the application needs to distinguish the condition as a meaningful domain event, a custom exception may be appropriate.

For example:

Main.java
Run Online
throw new InsufficientBalanceException( "Insufficient balance");

The exception type itself carries useful business meaning.


Custom Exception Design Example

A clean application might have:

Architecture Diagram
exception/
├── ApplicationException.java
├── UserNotFoundException.java
├── PaymentException.java
├── PaymentTimeoutException.java
├── OrderException.java
└── InsufficientBalanceException.java

A possible hierarchy:

Architecture Diagram
RuntimeException
      │
      └── ApplicationException
              │
              ├── UserNotFoundException
              ├── OrderException
              └── PaymentException
                       │
                       └── PaymentTimeoutException

This 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.

Main.java
Run Online
class MyException extends Exception {
}

How do I create a checked custom exception?

Extend Exception:

Main.java
Run Online
class MyException extends Exception {
}

How do I create an unchecked custom exception?

Extend RuntimeException:

Main.java
Run Online
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.

Main.java
Run Online
public MyException(String message) {
    super(message);
}

Can a custom exception preserve another exception?

Yes. Use a cause:

Main.java
Run Online
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

  1. What is a custom exception?

  2. Which class can be extended to create a checked custom exception?

  3. Which class can be extended to create an unchecked custom exception?

  4. Why is super(message) used in an exception constructor?

  5. How do you throw a custom exception?

Intermediate

  1. What is the difference between a checked and unchecked custom exception?

  2. Why should custom exception names describe the actual failure?

  3. How can you preserve the original cause when wrapping an exception?

  4. Can a custom exception have multiple constructors?

  5. Why might an application create an InsufficientBalanceException?

Advanced

  1. How can custom exceptions form an inheritance hierarchy?

  2. When should an application use a built-in exception instead of a custom exception?

  3. How can custom exceptions be used across service and repository layers?

  4. Why can excessive custom exceptions make an application harder to maintain?

  5. How can a REST application convert a custom exception into an HTTP response?


Quick Revision

Main.java
Run Online
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 layer

Checked custom exception

Main.java
Run Online
class PaymentException extends Exception {
}

Unchecked custom exception

Main.java
Run Online
class PaymentException extends RuntimeException {
}

Throwing a custom exception

Main.java
Run Online
throw new PaymentException( "Payment failed");

Declaring a checked custom exception

Main.java
Run Online
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

Main.java
Run Online
Existing Java Exception↓Extend the class↓Custom Exception Class↓throw when needed↓Handle the error

Graphic 2: Checked vs Unchecked

Architecture Diagram
              Custom Exception
                     │
          ┌──────────┴──────────┐
↓

↓
Exception          RuntimeException
↓

↓
Checked              Unchecked

Graphic 3: Banking Example

Main.java
Run Online
Withdraw ₹7,000↓Balance = ₹5,000↓Amount > Balance↓InsufficientBalanceException↓Application handles error

Graphic 4: Exception Hierarchy

Architecture Diagram
RuntimeException
       │
       └── ApplicationException
               │
               ├── UserNotFoundException
               ├── OrderException
               └── PaymentException
                       │
                       └── PaymentTimeoutException

Graphic 5: throw + throws + Custom Exception

Main.java
Run Online
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+
Main.java
Run Online
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());
        }
    }
}

Common Java Technical Interview Questions & Answers

Interview Prep
Q1

When should a custom exception extend RuntimeException vs Exception?

Extend RuntimeException for programming errors and recoverable business conditions where callers shouldn't be burdened with boilerplate checked handling; extend Exception when callers must explicitly recover from a known checked condition.
Q2

Why should custom exceptions provide a constructor accepting a Throwable cause?

To support exception chaining (preserving root cause stack traces).

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Create domain-specific runtime exceptions (e.g. PaymentFailedException) that extend RuntimeException and pass error context variables.