← Back to Home

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