Custom Exceptions
Custom exceptions are user-defined exception classes created to represent application-specific error conditions. They make error handling clearer, more meaningful, and domain-focused instead of relying on generic Java exceptions.
This is a high-frequency interview topic, especially in real-world and enterprise Java.
What Is a Custom Exception?
- A class created by the programmer
- Extends Exception (checked) or RuntimeException (unchecked)
- Represents business-rule violations
- Improves readability and maintainability
class InvalidAgeException extends Exception {
}
Why Custom Exceptions Are Needed
- Generic exceptions lack business meaning
- Improves error clarity
- Enables domain-specific handling
- Cleaner APIs and logs
- Better debugging and maintenance
❌ Bad practice:
throw new Exception("Error");
✔ Good practice:
throw new InvalidAgeException("Age must be >= 18");
Types of Custom Exceptions
1️⃣ Checked Custom Exception
Extends Exception. Must be handled or declared.
class InvalidAgeException extends Exception {
InvalidAgeException(String message) {
super(message);
}
}
Usage
void validateAge(int age) throws InvalidAgeException {
if (age < 18) {
throw new InvalidAgeException("Not eligible to vote");
}
}
2️⃣ Unchecked Custom Exception
Extends RuntimeException. Not forced to be handled.
class InvalidAgeException extends RuntimeException {
InvalidAgeException(String message) {
super(message);
}
}
Usage
void validateAge(int age) {
if (age < 18) {
throw new InvalidAgeException("Age below 18");
}
}
Checked vs Unchecked Custom Exceptions
| Aspect | Checked | Unchecked |
|---|---|---|
| Parent class | Exception | RuntimeException |
| Compiler check | ✅ Yes | ❌ No |
| Must handle | ✅ Yes | ❌ No |
| Use case | Recoverable | Programming / rule violation |
Best Practice: Add Constructors
class BusinessException extends Exception {
BusinessException() {
super();
}
BusinessException(String message) {
super(message);
}
BusinessException(String message, Throwable cause) {
super(message, cause);
}
}
- ✔ Supports exception chaining
- ✔ Production-ready design
Exception Chaining (Important)
try {
int x = 10 / 0;
} catch (ArithmeticException e) {
throw new BusinessException("Calculation failed", e);
}
- ✔ Preserves original cause
- ✔ Helps debugging
Naming Conventions
- Must end with Exception
- Should describe error condition clearly
Examples:
- InvalidAgeException
- InsufficientBalanceException
- UserNotFoundException
Real-World Example (Interview-Friendly)
class InsufficientBalanceException extends Exception {
InsufficientBalanceException(String msg) {
super(msg);
}
}
class BankAccount {
private double balance = 5000;
void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
throw new InsufficientBalanceException("Insufficient funds");
}
balance -= amount;
}
}
When to Use Custom Exceptions
- Business rule violations
- Validation failures
- Domain-specific errors
- Service and API layers
When NOT to Use Custom Exceptions
- For normal control flow
- When standard exceptions already fit
- For trivial or generic failures
Common Beginner Mistakes
- Extending Throwable or Error
- Creating too many custom exceptions
- Using checked exceptions everywhere
- Not passing meaningful messages
- Ignoring exception chaining
Interview-Ready Answers
Short Answer
A custom exception is a user-defined exception used to represent application-specific error conditions.
Detailed Answer
In Java, custom exceptions are created by extending Exception or RuntimeException. They allow developers to represent business-specific errors clearly and handle them meaningfully. Checked custom exceptions must be handled or declared, while unchecked ones are optional.
Key Takeaway
Custom exceptions express business intent. They turn error handling from a technical necessity into a clear, readable, and maintainable design feature.
More Custom Exception Examples
1. Basic Custom Checked Exception
class InvalidAgeException extends Exception {
InvalidAgeException(String msg) {
super(msg);
}
}
class Demo {
static void checkAge(int age) throws InvalidAgeException {
if (age < 18) {
throw new InvalidAgeException("Age below 18");
}
System.out.println("Eligible");
}
public static void main(String[] args) throws InvalidAgeException {
checkAge(16);
}
}
Output
Exception in thread "main" InvalidAgeException: Age below 18
2. Handling Custom Checked Exception
class InvalidAgeException extends Exception {
InvalidAgeException(String msg) {
super(msg);
}
}
class Demo {
static void checkAge(int age) throws InvalidAgeException {
if (age < 18) {
throw new InvalidAgeException("Too young");
}
}
public static void main(String[] args) {
try {
checkAge(15);
} catch (InvalidAgeException e) {
System.out.println(e.getMessage());
}
}
}
Output
Too young
3. Custom Unchecked Exception (Extends RuntimeException)
class InvalidAmountException extends RuntimeException {
InvalidAmountException(String msg) {
super(msg);
}
}
class Demo {
static void pay(int amt) {
if (amt <= 0) {
throw new InvalidAmountException("Amount must be positive");
}
System.out.println("Payment done");
}
public static void main(String[] args) {
pay(-10);
}
}
Output
Exception in thread "main" InvalidAmountException: Amount must be positive
4. Handling Custom Unchecked Exception
class InvalidAmountException extends RuntimeException {
InvalidAmountException(String msg) {
super(msg);
}
}
class Demo {
public static void main(String[] args) {
try {
throw new InvalidAmountException("Invalid payment");
} catch (InvalidAmountException e) {
System.out.println("Handled: " + e.getMessage());
}
}
}
Output
Handled: Invalid payment
5. Custom Exception with Default Constructor
class DataNotFoundException extends Exception {
DataNotFoundException() {
super("Data not found");
}
}
class Demo {
public static void main(String[] args) throws DataNotFoundException {
throw new DataNotFoundException();
}
}
6. Custom Exception with Multiple Constructors
class AppException extends Exception {
AppException() {}
AppException(String msg) {
super(msg);
}
AppException(String msg, Throwable cause) {
super(msg, cause);
}
}
Explanation
Supports flexibility and chaining.
7. Exception Chaining (Cause)
class ServiceException extends Exception {
ServiceException(String msg, Throwable cause) {
super(msg, cause);
}
}
class Demo {
public static void main(String[] args) {
try {
int a = 10 / 0;
} catch (ArithmeticException e) {
throw new RuntimeException(
new ServiceException("Service failed", e)
);
}
}
}
Explanation
Preserves root cause.
8. Custom Exception Thrown from Constructor
class InvalidConfigException extends Exception {
InvalidConfigException(String msg) {
super(msg);
}
}
class Config {
Config(String env) throws InvalidConfigException {
if (!"PROD".equals(env)) {
throw new InvalidConfigException("Invalid environment");
}
}
public static void main(String[] args) throws InvalidConfigException {
new Config("DEV");
}
}
9. Custom Exception in Method Overriding (Checked)
class Base {
void process() throws Exception {}
}
class Child extends Base {
void process() throws Exception {} // same exception allowed
}
10. Overriding with Narrower Custom Exception
class AppException extends Exception {}
class SubAppException extends AppException {}
class Base {
void run() throws AppException {}
}
class Child extends Base {
void run() throws SubAppException {}
}
11. Overriding with Broader Custom Exception (Not Allowed)
class AppException extends Exception {}
class Base {
void run() throws AppException {}
}
class Child extends Base {
// void run() throws Exception {} // ❌ not allowed
}
12. Custom Exception with Error Code
class BusinessException extends Exception {
int code;
BusinessException(int code, String msg) {
super(msg);
this.code = code;
}
}
class Demo {
public static void main(String[] args) {
try {
throw new BusinessException(401, "Unauthorized");
} catch (BusinessException e) {
System.out.println(e.code + " - " + e.getMessage());
}
}
}
Output
401 - Unauthorized
13. Custom Exception as Validation Failure
class ValidationException extends RuntimeException {
ValidationException(String msg) {
super(msg);
}
}
class Demo {
static void validate(String name) {
if (name == null || name.isEmpty()) {
throw new ValidationException("Name required");
}
}
public static void main(String[] args) {
validate("");
}
}
14. Custom Exception in Interface Contract
class AuthException extends Exception {
AuthException(String msg) {
super(msg);
}
}
interface AuthService {
void login(String user) throws AuthException;
}
class ServiceImpl implements AuthService {
public void login(String user) throws AuthException {
if (!"admin".equals(user)) {
throw new AuthException("Invalid user");
}
}
}
15. Custom Exception + finally
class MyException extends Exception {}
class Demo {
public static void main(String[] args) {
try {
throw new MyException();
} catch (MyException e) {
System.out.println("Caught");
} finally {
System.out.println("Cleanup");
}
}
}
Output
Caught
Cleanup
16. Wrapping Custom Checked into Unchecked
class DataException extends Exception {
DataException(String msg) {
super(msg);
}
}
class Demo {
static void load() {
try {
throw new DataException("DB error");
} catch (DataException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
load();
}
}
17. Custom Exception vs Error (Do NOT Extend Error)
class MyError extends Error {} // ❌ bad practice
Explanation
Custom logic exceptions should extend Exception or RuntimeException, not Error.
18. Custom Exception in Lambda (Unchecked Only)
interface Task {
void run();
}
class MyRuntimeException extends RuntimeException {}
class Demo {
public static void main(String[] args) {
Task t = () -> {
throw new MyRuntimeException();
};
t.run();
}
}
19. Custom Exception with Logging Pattern
class ServiceException extends RuntimeException {
ServiceException(String msg) {
super(msg);
}
}
class Demo {
static void process() {
try {
int a = 10 / 0;
} catch (Exception e) {
System.err.println("ERROR: " + e.getMessage());
throw new ServiceException("Process failed");
}
}
public static void main(String[] args) {
process();
}
}
20. Interview Summary – Custom Exceptions
class MyException extends Exception {
MyException(String msg) {
super(msg);
}
}
class Demo {
static void test() throws MyException {
throw new MyException("Custom failure");
}
public static void main(String[] args) throws MyException {
test();
}
}
Key Points
- Extend Exception → checked
- Extend RuntimeException → unchecked
- Used for business rules & validations
- Improves readability & error clarity