By WebNest Studio Engineering Team
Java - Core Tutorial
Custom Exceptions
Java's built-in exceptions cover generic problems like invalid arguments or null references, but real applications often have domain-specific failure conditions — an insufficient account balance, an invalid order state, a seat that is already booked. Custom exceptions let you represent these situations as distinct, self-documenting types instead of overloading generic exceptions or plain error codes.
Why it matters
A generic Exception("error") tells the caller nothing. A named exception such as InsufficientFundsException says exactly what went wrong, can carry the relevant data, and can be caught separately from everything else. Well-chosen custom exceptions make an application's failures part of its design.
Extending Exception vs RuntimeException
A custom exception is created by extending an existing exception class. Extending Exception creates a checked exception, forcing every caller to handle it or declare it — appropriate when callers can reasonably be expected to recover, such as a business rule violation that the calling code should specifically respond to. Extending RuntimeException creates an unchecked exception, which callers are not forced to handle — appropriate for programming errors or conditions that indicate a bug rather than an expected, recoverable situation.
Calling super(message)
A well-designed custom exception provides at least one constructor that accepts a message and passes it to the parent class using super(message), so that getMessage() works correctly and the message appears in stack traces. It's also good practice to provide a constructor accepting a Throwable cause, passed as super(message, cause), so the original underlying exception isn't lost when you wrap and re-throw it as your custom type.
public InsufficientFundsException(String message) { super(message); }public InsufficientFundsException(String message, Throwable cause) { super(message, cause); }- Naming convention: custom exception class names should end in "Exception".
When to Make an Exception Checked vs Unchecked
Choose checked when failure is an expected, recoverable part of the domain and callers genuinely need to be reminded to handle it — for example, a custom SeatAlreadyBookedException in a booking system, which calling code should catch and respond to (offer another seat). Choose unchecked when the failure represents a violated precondition or programming bug that shouldn't force every caller up the chain to add boilerplate handling, such as a custom InvalidConfigurationException thrown when a developer misconfigures a service at startup.
Example
A checked custom exception used to enforce handling of a business rule
class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
public class CustomExceptionDemo {
static void withdraw(double balance, double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(
"Cannot withdraw " + amount + "; balance is only " + balance);
}
System.out.println("Withdrawal successful: " + amount);
}
public static void main(String[] args) {
try {
withdraw(100.0, 250.0);
} catch (InsufficientFundsException e) {
System.out.println("Transaction failed: " + e.getMessage());
}
}
}
Transaction failed: Cannot withdraw 250.0; balance is only 100.0
Common Mistakes
- Extending Throwable directly instead of Exception or RuntimeException, which bypasses standard exception-handling idioms.
- Forgetting to call super(message) in the custom exception constructor, so getMessage() returns null and stack traces lose context.
- Making every custom exception checked "to be safe," which forces unnecessary try-catch boilerplate throughout the codebase for conditions that aren't truly recoverable.
- Swallowing the original cause when wrapping an exception, instead of passing it via super(message, cause) so it still appears in the stack trace.
Exercise
Create a checked exception InvalidCouponException. discountFor should return 10 for the code SAVE10 and throw the exception for any other code, with a message naming the code.
Expected output: 10 then Invalid coupon: FREE
// TODO: class InvalidCouponException extends Exception, with a message constructor
public class CouponDemo {
static int discountFor(String code) throws InvalidCouponException {
// TODO: return 10 for "SAVE10", otherwise throw the exception
return 0;
}
public static void main(String[] args) {
try {
System.out.println(discountFor("SAVE10"));
System.out.println(discountFor("FREE"));
} catch (InvalidCouponException e) {
System.out.println(e.getMessage());
}
}
}
Show hints
- The constructor should pass its message to the parent with
super(message);. - Compare strings with
equals, not==.
Show solution
class InvalidCouponException extends Exception {
InvalidCouponException(String message) {
super(message);
}
}
public class CouponDemo {
static int discountFor(String code) throws InvalidCouponException {
if ("SAVE10".equals(code)) {
return 10;
}
throw new InvalidCouponException("Invalid coupon: " + code);
}
public static void main(String[] args) {
try {
System.out.println(discountFor("SAVE10"));
System.out.println(discountFor("FREE"));
} catch (InvalidCouponException e) {
System.out.println(e.getMessage());
}
}
}
Quick quiz
1. Which class do you extend to create a checked custom exception?
- A. RuntimeException
- B. Object
- C. Error
- D. Exception
Show answer
D. A class that extends Exception, but not RuntimeException, is checked: callers must catch or declare it.
2. How does a custom exception pass its message to the parent class?
- A. By calling super(message) in its constructor
- B. By assigning this.message
- C. By overriding toString()
- D. It happens automatically
Show answer
A. super(message) stores the message in Throwable, so getMessage() and the stack trace show it.
3. Which class do you extend to create an unchecked custom exception?
- A. Exception
- B. RuntimeException
- C. Throwable
- D. Error
Show answer
B. Subclasses of RuntimeException are unchecked and need no throws declaration or catch block.
Interview questions
When should you create a custom exception?
Show answer
When a failure is specific to your application and callers may want to handle it differently from other errors: an invalid coupon, insufficient funds, a record that was not found. A custom type gives the failure a clear name and can carry extra fields. If a standard exception such as IllegalArgumentException already describes the problem, use that instead.
What is exception chaining?
Show answer
Wrapping a low-level exception inside a higher-level one while keeping the original as the cause: throw new OrderFailedException("Could not save order", e);. The caller sees an exception that makes sense at its level, and the stack trace still shows the original problem under "Caused by". Always pass the cause on, or the root reason is lost.
Key Points to Remember
- Extend Exception for a checked custom exception; extend RuntimeException for an unchecked one.
- Always provide a constructor that forwards the message (and ideally a cause) to the superclass via super(...).
- Use checked exceptions for expected, recoverable domain failures; use unchecked exceptions for programming errors or broken preconditions.
- Custom exception class names conventionally end in "Exception" for readability.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering