Custom Exception in Java – Create Your Own Exceptions
Java's built-in exceptions describe general problems such as a null reference or a missing file. But your application has its own kinds of failure: an account without enough balance, a product that does not exist, an order that cannot ship. A custom exception (also called a user-defined exception) gives each of these problems a clear name and can carry extra information about what went wrong.
This tutorial shows how to create checked and unchecked custom exceptions, add useful data and constructors, build a small exception hierarchy, and follow the best practices for designing exceptions.
1. How to Create a Custom Exception
A custom exception is a class that extends Exception (checked) or RuntimeException (unchecked):
// Checked: callers must catch or declare it
public class InvalidOrderException extends Exception {
public InvalidOrderException(String message) { super(message); }
public InvalidOrderException(String message, Throwable cause) { super(message, cause); }
}
// Unchecked: no throws clause needed
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException(String message) { super(message); }
}
Calling super(message) stores the message so that getMessage() and stack traces show it. The constructor with a cause lets you wrap another exception.
2. A Checked Custom Exception with Extra Data
Custom exceptions can carry fields that help callers react intelligently, not just a message:
public class CheckedCustom {
// A checked custom exception: callers MUST handle or declare it
static class InsufficientBalanceException extends Exception {
private final double shortfall;
InsufficientBalanceException(double requested, double available) {
super("Requested " + requested + " but only " + available + " is available");
this.shortfall = requested - available;
}
double getShortfall() { return shortfall; }
}
static double balance = 500;
static void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) throw new InsufficientBalanceException(amount, balance);
balance -= amount;
System.out.println("Withdrawn " + amount + ", remaining " + balance);
}
public static void main(String[] args) {
try {
withdraw(200);
withdraw(450);
} catch (InsufficientBalanceException e) {
System.out.println("Failed: " + e.getMessage());
System.out.println("You need " + e.getShortfall() + " more");
}
}
}
Withdrawn 200.0, remaining 300.0 Failed: Requested 450.0 but only 300.0 is available You need 150.0 more
Because it extends Exception, withdraw() must declare it, and the compiler makes every caller deal with it.
3. An Unchecked Custom Exception
import java.util.Map;
public class UncheckedCustom {
// An unchecked custom exception: no throws clause needed
static class ProductNotFoundException extends RuntimeException {
ProductNotFoundException(String sku) {
super("No product with SKU " + sku);
}
}
static final Map<String, Double> PRICES = Map.of("PEN-1", 20.0, "BAG-7", 899.0);
static double priceOf(String sku) {
Double price = PRICES.get(sku);
if (price == null) throw new ProductNotFoundException(sku);
return price;
}
public static void main(String[] args) {
System.out.println("PEN-1 costs " + priceOf("PEN-1"));
try {
priceOf("CUP-3");
} catch (ProductNotFoundException e) {
System.out.println(e.getMessage());
}
}
}
PEN-1 costs 20.0 No product with SKU CUP-3
No throws clause is needed, so methods stay simple, but callers are also not reminded to handle it. Document it with a Javadoc @throws tag.
4. Checked or Unchecked?
Choose checked (extends Exception) when… | Choose unchecked (extends RuntimeException) when… |
|---|---|
| The caller can reasonably recover (retry, ask the user, use another option) | The problem is a programming error or invalid input that should be fixed |
| You want the compiler to force callers to think about it | Most callers cannot do anything useful about it |
| It is part of a library API contract | You use frameworks such as Spring that handle exceptions centrally |
Many modern codebases prefer unchecked custom exceptions and handle them at application boundaries, but both approaches are valid.
5. Building an Exception Hierarchy
A base exception for an area of your application, with more specific subclasses, lets callers catch broadly or precisely as needed:
public class ExceptionHierarchyDemo {
// A small hierarchy lets callers catch broadly or precisely
static class OrderException extends RuntimeException {
OrderException(String message) { super(message); }
OrderException(String message, Throwable cause) { super(message, cause); }
}
static class OutOfStockException extends OrderException {
OutOfStockException(String item) { super(item + " is out of stock"); }
}
static class PaymentDeclinedException extends OrderException {
PaymentDeclinedException(String reason, Throwable cause) { super("Payment declined: " + reason, cause); }
}
static void placeOrder(String item, String card) {
if (item.equals("lamp")) throw new OutOfStockException(item);
try {
if (card.length() != 16) throw new IllegalArgumentException("card number must have 16 digits");
} catch (IllegalArgumentException e) {
throw new PaymentDeclinedException("invalid card", e); // wrap with cause
}
System.out.println("Order placed for " + item);
}
public static void main(String[] args) {
String[][] orders = {{"chair", "1234567812345678"}, {"lamp", "1234567812345678"}, {"desk", "1234"}};
for (String[] o : orders) {
try {
placeOrder(o[0], o[1]);
} catch (OutOfStockException e) { // precise handling
System.out.println("Suggest alternatives: " + e.getMessage());
} catch (OrderException e) { // everything else about orders
System.out.println(e.getMessage() + " (cause: " + e.getCause().getMessage() + ")");
}
}
}
}
Order placed for chair Suggest alternatives: lamp is out of stock Payment declined: invalid card (cause: card number must have 16 digits)
The specific OutOfStockException gets special handling; all other order problems fall through to the OrderException catch block. The payment exception keeps the original IllegalArgumentException as its cause.
6. Best Practices
- Name it after the problem and end the name with
Exception:InsufficientBalanceException, notBalanceError. - Provide the standard constructors: message, and message plus cause.
- Always keep the cause when wrapping another exception.
- Add fields for data callers need (amount, ID, error code) instead of making them parse the message.
- Reuse standard exceptions when they fit (
IllegalArgumentException,IllegalStateException); do not create custom exceptions for everything. - Keep exceptions immutable and lightweight; avoid heavy work in constructors.
- Do not extend
ThrowableorErrordirectly. - Add a
serialVersionUIDif your exceptions may be serialized (for example across remote calls).
7. Summary
- Create a custom exception by extending
Exception(checked) orRuntimeException(unchecked). - Pass the message (and cause) to
super(...)in its constructors. - Add fields to carry useful details about the failure.
- Organise related exceptions in a hierarchy so callers can catch at the right level.
- Use custom exceptions for domain-specific problems; reuse standard ones otherwise.
Related Tutorials
Frequently Asked Questions (FAQ)
Q1. What is a custom exception in Java?
An exception class you define yourself, by extending Exception or RuntimeException, to represent an error specific to your application.
Q2. How do I create a custom checked exception?
Extend Exception: class MyException extends Exception { MyException(String msg) { super(msg); } }. Methods that throw it must declare it with throws.
Q3. How do I create a custom unchecked exception?
Extend RuntimeException. It can be thrown without a throws clause, and callers are not forced to catch it.
Q4. Why create custom exceptions?
To make errors self-explanatory, to carry extra details, and to let callers handle specific problems precisely instead of catching generic exceptions.
Q5. Should a custom exception extend Exception or RuntimeException?
Extend Exception for recoverable conditions that callers must consider; extend RuntimeException for programming errors or when callers usually cannot recover.
Q6. Why call super(message) in the constructor?
So that the message is stored in the Throwable and returned by getMessage() and printed in stack traces.
Q7. Why add a constructor with a cause?
To wrap a lower-level exception while keeping it available via getCause() and in the stack trace, which is essential for debugging.
Q8. Can custom exceptions have extra fields?
Yes. Fields such as an amount, an ID or an error code, with getters, let callers react without parsing the message.
Q9. Can I extend Throwable directly?
You can, but you should not. Extend Exception or RuntimeException so that normal catch blocks and frameworks handle your exception correctly.
Q10. Do custom exceptions need serialVersionUID?
Throwable is Serializable, so the compiler may warn without one. Add it if your exceptions can be serialized, for example sent between services.
Q11. How do I throw a custom exception?
Create and throw it like any exception: throw new InsufficientBalanceException(amount, balance);.
Q12. Can a custom exception extend another custom exception?
Yes. Building a hierarchy, such as OrderException with subclasses OutOfStockException and PaymentDeclinedException, lets callers catch at different levels.
Q13. How should I name custom exceptions?
Describe the problem and end with Exception, for example UserNotFoundException or InvalidCouponException.
Q14. Should I create a custom exception for every error?
No. Reuse standard exceptions such as IllegalArgumentException when they describe the problem well, and create custom ones for meaningful domain errors.
Q15. Can a custom exception override getMessage()?
Yes, for example to build a message from fields lazily. Usually it is simpler to build the message in the constructor and pass it to super.
Q16. How are custom exceptions used in Spring Boot?
Services throw unchecked custom exceptions, and a @ControllerAdvice class with @ExceptionHandler methods turns them into HTTP responses with proper status codes.
Q17. Can a record be an exception?
No. Records cannot extend classes, and exceptions must extend Throwable. Use a normal final class with final fields.
Q18. Should exception constructors do heavy work?
No. Creating an exception should be cheap and must not fail. Avoid I/O, database calls or complex logic in exception constructors.
Q19. Can I disable the stack trace of a custom exception?
Yes, using the protected Throwable constructor with writableStackTrace = false. This is useful for very frequent, expected exceptions, but you lose debugging information.
Q20. What is the difference between a custom exception and a built-in exception?
Built-in exceptions come with the JDK and describe general problems. Custom exceptions are written by you and describe problems specific to your application's domain.