Creating Exception Classes

Introduction

Custom exceptions allow you to create your own exception types for specific error conditions.


Why Create Custom Exceptions?

  1. Specific errors for your application
  2. Better error handling
  3. Meaningful names
  4. Additional information

Basic Syntax

// Checked exception
class MyException extends Exception {
    MyException(String message) {
        super(message);
    }
}

// Unchecked exception
class MyRuntimeException extends RuntimeException {
    MyRuntimeException(String message) {
        super(message);
    }
}

Simple Custom Exception

// Custom checked exception
class InvalidAgeException extends Exception {
    InvalidAgeException(String message) {
        super(message);
    }
}

class Person {
    int age;

    void setAge(int age) throws InvalidAgeException {
        if (age < 0 || age > 150) {
            throw new InvalidAgeException("Age must be between 0 and 150");
        }
        this.age = age;
    }
}

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        try {
            p.setAge(200);
        } catch (InvalidAgeException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}

Checked vs Unchecked Custom Exception

Checked (extends Exception):

class InsufficientFundsException extends Exception {
    InsufficientFundsException(String message) {
        super(message);
    }
}

// Must declare with throws
void withdraw(double amount) throws InsufficientFundsException {
    if (amount > balance) {
        throw new InsufficientFundsException("Not enough funds");
    }
    balance -= amount;
}

Unchecked (extends RuntimeException):

class InvalidAmountException extends RuntimeException {
    InvalidAmountException(String message) {
        super(message);
    }
}

// No need to declare
void deposit(double amount) {
    if (amount <= 0) {
        throw new InvalidAmountException("Amount must be positive");
    }
    balance += amount;
}

With Additional Information

class AccountException extends Exception {
    private String accountNumber;
    private double attemptedAmount;

    AccountException(String message, String accountNumber, double attemptedAmount) {
        super(message);
        this.accountNumber = accountNumber;
        this.attemptedAmount = attemptedAmount;
    }

    String getAccountNumber() {
        return accountNumber;
    }

    double getAttemptedAmount() {
        return attemptedAmount;
    }
}

class BankAccount {
    String accountNumber;
    double balance;

    void withdraw(double amount) throws AccountException {
        if (amount > balance) {
            throw new AccountException(
                "Insufficient funds",
                accountNumber,
                amount
            );
        }
        balance -= amount;
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount();
        account.accountNumber = "ACC001";
        account.balance = 1000;

        try {
            account.withdraw(1500);
        } catch (AccountException e) {
            System.out.println(e.getMessage());
            System.out.println("Account: " + e.getAccountNumber());
            System.out.println("Attempted: $" + e.getAttemptedAmount());
        }
    }
}

Multiple Constructors

class CustomException extends Exception {
    // No-arg constructor
    CustomException() {
        super("Custom exception occurred");
    }

    // Constructor with message
    CustomException(String message) {
        super(message);
    }

    // Constructor with message and cause
    CustomException(String message, Throwable cause) {
        super(message, cause);
    }
}

Complete Example: Student System

// Custom exceptions
class InvalidMarksException extends Exception {
    InvalidMarksException(String message) {
        super(message);
    }
}

class InvalidRollNumberException extends Exception {
    InvalidRollNumberException(String message) {
        super(message);
    }
}

class Student {
    private int rollNumber;
    private String name;
    private int marks;

    void setRollNumber(int rollNumber) throws InvalidRollNumberException {
        if (rollNumber <= 0) {
            throw new InvalidRollNumberException("Roll number must be positive");
        }
        this.rollNumber = rollNumber;
    }

    void setMarks(int marks) throws InvalidMarksException {
        if (marks < 0 || marks > 100) {
            throw new InvalidMarksException("Marks must be between 0 and 100");
        }
        this.marks = marks;
    }

    void setName(String name) {
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("Name cannot be empty");
        }
        this.name = name;
    }

    void display() {
        System.out.println("Roll: " + rollNumber);
        System.out.println("Name: " + name);
        System.out.println("Marks: " + marks);
    }
}

public class Main {
    public static void main(String[] args) {
        Student student = new Student();

        try {
            student.setName("John");
            student.setRollNumber(101);
            student.setMarks(85);
            student.display();

            student.setMarks(150);  // Invalid
        } catch (InvalidMarksException e) {
            System.out.println("Marks error: " + e.getMessage());
        } catch (InvalidRollNumberException e) {
            System.out.println("Roll number error: " + e.getMessage());
        } catch (IllegalArgumentException e) {
            System.out.println("Argument error: " + e.getMessage());
        }
    }
}

Exception Hierarchy

// Base custom exception
class ApplicationException extends Exception {
    ApplicationException(String message) {
        super(message);
    }
}

// Specific exceptions
class DatabaseException extends ApplicationException {
    DatabaseException(String message) {
        super(message);
    }
}

class ValidationException extends ApplicationException {
    ValidationException(String message) {
        super(message);
    }
}

class NetworkException extends ApplicationException {
    NetworkException(String message) {
        super(message);
    }
}

With Error Codes

class ErrorCodeException extends Exception {
    private int errorCode;

    ErrorCodeException(String message, int errorCode) {
        super(message);
        this.errorCode = errorCode;
    }

    int getErrorCode() {
        return errorCode;
    }
}

public class Main {
    static void process(int code) throws ErrorCodeException {
        if (code == 404) {
            throw new ErrorCodeException("Not found", 404);
        }
        if (code == 500) {
            throw new ErrorCodeException("Server error", 500);
        }
    }

    public static void main(String[] args) {
        try {
            process(404);
        } catch (ErrorCodeException e) {
            System.out.println("Error " + e.getErrorCode() + ": " + e.getMessage());
        }
    }
}

Best Practices

  1. Name convention: XxxException
  2. Extend appropriate parent (Exception or RuntimeException)
  3. Provide constructors for different use cases
  4. Add useful information via fields/methods
  5. Document when exception is thrown
  6. Don’t create too many custom exceptions

When to Use

Create custom exception when:

  • Domain-specific errors
  • Need additional information
  • Standard exceptions not sufficient
  • Better error categorization

Use standard exceptions when:

  • Common errors (null, illegal argument)
  • Standard behavior needed
  • No extra information required

Quick Reference

// Checked custom exception
class MyCheckedException extends Exception {
    MyCheckedException(String message) {
        super(message);
    }
}

// Unchecked custom exception
class MyUncheckedException extends RuntimeException {
    MyUncheckedException(String message) {
        super(message);
    }
}

// With additional data
class DetailedException extends Exception {
    private int code;

    DetailedException(String message, int code) {
        super(message);
        this.code = code;
    }

    int getCode() {
        return code;
    }
}

// Usage
void method() throws MyCheckedException {
    throw new MyCheckedException("Error occurred");
}

Common Mistakes

// ✗ Wrong - extending Error
class MyException extends Error {  // Don't extend Error
}

// ✗ Wrong - not calling super
class MyException extends Exception {
    MyException(String message) {
        // Missing super(message)
    }
}

// ✓ Correct
class MyException extends Exception {
    MyException(String message) {
        super(message);
    }
}

// ✗ Wrong - too generic name
class Exception1 extends Exception { }

// ✓ Correct - descriptive name
class InvalidPasswordException extends Exception { }

Exam Tips

Remember:

  1. Extend Exception for checked
  2. Extend RuntimeException for unchecked
  3. Call super() in constructor
  4. Naming: XxxException
  5. Provide multiple constructors
  6. Add custom fields for extra info
  7. Document with JavaDoc
  8. Use when standard exceptions insufficient
  9. Checked must be handled/declared
  10. Keep hierarchy organized

Common Questions:

  • How to create custom exception?
  • Checked vs unchecked custom?
  • Parent class for custom exception?
  • Multiple constructors?
  • Additional information in exception?
  • When to create custom exception?
  • Naming convention?
  • Exception hierarchy?
  • Best practices?
  • Common mistakes?