Your banking app needs to signal "insufficient funds". Using generic Exception loses meaning. Custom exceptions like InsufficientFundsException carry the balance and requested amount - meaningful error information.

Simple custom exception

Create your own exception type.

SimpleCustom.java
Replay: real traced execution (multi-file project)
// Creating Basic Custom Exceptions

public class SimpleCustom {
    public static void main(String[] args) {
        System.out.println("=== Basic Custom Exceptions ===\n");

        // Simple custom exception
        System.out.println("--- Simple Custom Exception ---");

        try {
            validateAge(-5);
        } catch (InvalidAgeException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        // Custom exception with valid input
        System.out.println("\n--- Valid Input ---");

        try {
            validateAge(25);
            System.out.println("Age 25 is valid!");
        } catch (InvalidAgeException e) {
            System.out.println("Error: " + e.getMessage());
        }

        // Another custom exception
        System.out.println("\n--- Another Custom Exception ---");

        try {
            validateUsername("ab");
        } catch (InvalidUsernameException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        // Multiple custom exceptions
        System.out.println("\n--- Multiple Custom Exceptions ---");

        String[] usernames = {"alice123", "x", "bob_smith", ""};
        int[] ages = {25, -1, 200, 30};

        for (int i = 0; i < usernames.length; i++) {
            try {
                validateUsername(usernames[i]);
                validateAge(ages[i]);
                System.out.println("✓ Valid: " + usernames[i] + ", age " + ages[i]);
            } catch (InvalidUsernameException e) {
                System.out.println("✗ Username error: " + e.getMessage());
            } catch (InvalidAgeException e) {
                System.out.println("✗ Age error: " + e.getMessage());
            }
        }

        System.out.println("\n=== Key Points ===");
        System.out.println("""
            1. Extend RuntimeException for unchecked
            2. Extend Exception for checked
            3. Call super(message) to set message
            4. Name should end with 'Exception'
            5. Name should describe the error clearly
            """);
    }

    static void validateAge(int age) {
        if (age < 0) {
            throw new InvalidAgeException("Age cannot be negative: " + age);
        }
        if (age > 150) {
            throw new InvalidAgeException("Age cannot exceed 150: " + age);
        }
    }

    static void validateUsername(String username) {
        if (username == null || username.isEmpty()) {
            throw new InvalidUsernameException("Username cannot be empty");
        }
        if (username.length() < 3) {
            throw new InvalidUsernameException("Username must be at least 3 characters: " + username);
        }
    }
}

// Simple custom exception - unchecked
class InvalidAgeException extends RuntimeException {
    public InvalidAgeException(String message) {
        super(message);
    }
}

// Another simple custom exception
class InvalidUsernameException extends RuntimeException {
    public InvalidUsernameException(String message) {
        super(message);
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
  1. public static void main(String[] args)

    3public class SimpleCustom {4    public static void main(String[] args) {5        System.out.println("=== Basic Custom Exceptions ===\n");67        // Simple custom exception //?simple_demo8        System.out.println("--- Simple Custom Exception ---");
    output=== Basic Custom Exceptions ===
    --- Simple Custom Exception ---
  2. static void validateAge(int age)

    pass 1 of 4
    63static void validateAge(int age-5) { //?validate_age_method64    if (age < 0) { //?check_negative
    All 4 passes — pass 1 is the card above
    passagemessageeages[i]iusernames[i]username
    1-5Age cannot be negative: -5InvalidAgeException: Age cannot be negative: -5
    225InvalidUsernameException: Username must be at least 3 characters: ab
    325InvalidUsernameException: Username must be at least 3 characters: x250alice123
    4200Age cannot exceed 150: 200InvalidAgeException: Age cannot exceed 150: 200(empty)
  3. if (age < 0)

    63static void validateAge(int age) { //?validate_age_method64    if (age-5 < 0) { //?check_negative65        throw new InvalidAgeException("Age cannot be negative: " + age); //?throw_negative66    }
  4. public InvalidAgeException(String message)

    pass 1 of 2
    83class InvalidAgeException extends RuntimeException { //?invalid_age_def84    public InvalidAgeException(String messageAge cannot be negative: -5) { //?invalid_age_constructor85        super(message); //?super_call
  5. catch (InvalidAgeException e)

    11    validateAge(-5); //?call_validate_age12} catch (InvalidAgeException eInvalidAgeException: Age cannot be negative: -5) { //?catch_simple13    System.out.println("Caught: " + e.getMessage()); //?print_simple14}
    outputCaught: Age cannot be negative: -5
  6. System.out.println(" --- Valid Input ---");

    16// Custom exception with valid input //?valid_input17System.out.println("\n--- Valid Input ---");
    output
    --- Valid Input ---
  7. static void validateUsername(String username)

    pass 1 of 5
    72static void validateUsername(String usernameab) { //?validate_username_method73    if (username == null || username.isEmpty()) { //?check_empty
    All 5 passes — pass 1 is the card above
    passusernameeusernames[i]iages[i]agemessage
    1abInvalidUsernameException: Username must be at least 3 characters: ab
    2alice123alice123025
    3xInvalidUsernameException: Username must be at least 3 characters: x
    4bob_smithInvalidAgeException: Age cannot exceed 150: 200bob_smith2200200Age cannot exceed 150: 200
    5(empty)InvalidUsernameException: Username cannot be empty
  8. public InvalidUsernameException(String message)

    pass 1 of 3
    90class InvalidUsernameException extends RuntimeException { //?invalid_username_def91    public InvalidUsernameException(String messageUsername must be at least 3 characters: ab) { //?invalid_username_constructor92        super(message); //?super_call_username
    All 3 passes — pass 1 is the card above
    passmessageeageusername
    1Username must be at least 3 characters: abInvalidUsernameException: Username must be at least 3 characters: ab
    2Username must be at least 3 characters: xInvalidUsernameException: Username must be at least 3 characters: x200(empty)
    3Username cannot be emptyInvalidUsernameException: Username cannot be empty
  9. catch (InvalidUsernameException e)

    30    validateUsername("ab"); //?call_validate_username31} catch (InvalidUsernameException eInvalidUsernameException: Username must be at least 3 characters: ab) { //?catch_username32    System.out.println("Caught: " + e.getMessage()); //?print_username33}
    outputCaught: Username must be at least 3 characters: ab
  10. String[] usernames = {"alice123", "x", "bob_smith", ""}; //?usernames

    35// Multiple custom exceptions //?multiple_custom36System.out.println("\n--- Multiple Custom Exceptions ---");3738String[] usernames = {"alice123", "x", "bob_smith", ""}; //?usernames39int[] ages = {25, -1, 200, 30}; //?ages
    output
    --- Multiple Custom Exceptions ---
  11. for (int i = 0; i < usernames.length; i++)

    pass 1 of 4
    41for (int i0 = 0; i < usernames.length4; i++) { //?loop_validate42    try { //?try_both
    All 4 passes — pass 1 is the card above
    passieagemessageusername
    10
    21InvalidUsernameException: Username must be at least 3 characters: x
    32InvalidAgeException: Age cannot exceed 150: 200200Age cannot exceed 150: 200
    43InvalidUsernameException: Username cannot be empty(empty)
  12. try

    pass 1 of 4
    41for (int i = 0; i < usernames.length; i++) { //?loop_validate42    try { //?try_both43        validateUsername(usernames[i]alice123); //?validate_user44        validateAge(ages[i]); //?validate_age_loop
    All 4 passes — pass 1 is the card above
    passusernames[i]ieagemessageusername
    1alice1230
    2x1InvalidUsernameException: Username must be at least 3 characters: x
    3bob_smith2InvalidAgeException: Age cannot exceed 150: 200200Age cannot exceed 150: 200
    4(empty)3InvalidUsernameException: Username cannot be empty(empty)
  13. catch (InvalidUsernameException e)

    pass 1 of 2
    45    System.out.println("✓ Valid: " + usernames[i] + ", age " + ages[i]); //?print_valid46} catch (InvalidUsernameException eInvalidUsernameException: Username must be at least 3 characters: x) { //?catch_user47    System.out.println("✗ Username error: " + e.getMessage()); //?print_user_error48} catch (InvalidAgeException e) { //?catch_age
    output✗ Username error: Username must be at least 3 characters: x
  14. if (age > 150)

    66}67if (age200 > 150) { //?check_max68    throw new InvalidAgeException("Age cannot exceed 150: " + age); //?throw_max69}
  15. public InvalidAgeException(String message)

    pass 2 of 2
    83class InvalidAgeException extends RuntimeException { //?invalid_age_def84    public InvalidAgeException(String messageAge cannot exceed 150: 200) { //?invalid_age_constructor85        super(message); //?super_call
  16. catch (InvalidAgeException e)

    47    System.out.println("✗ Username error: " + e.getMessage()); //?print_user_error48} catch (InvalidAgeException eInvalidAgeException: Age cannot exceed 150: 200) { //?catch_age49    System.out.println("✗ Age error: " + e.getMessage()); //?print_age_error50}
    output✗ Age error: Age cannot exceed 150: 200
  17. if (username == null || username.isEmpty())

    72static void validateUsername(String username) { //?validate_username_method73    if (username(empty) == null || username.isEmpty()) { //?check_empty74        throw new InvalidUsernameException("Username cannot be empty"); //?throw_empty75    }
  18. catch (InvalidUsernameException e)

    pass 2 of 2
    45    System.out.println("✓ Valid: " + usernames[i] + ", age " + ages[i]); //?print_valid46} catch (InvalidUsernameException eInvalidUsernameException: Username cannot be empty) { //?catch_user47    System.out.println("✗ Username error: " + e.getMessage()); //?print_user_error48} catch (InvalidAgeException e) { //?catch_age
    output✗ Username error: Username cannot be empty
  19. System.out.println(" === Key Points ===");

    53    System.out.println("\n=== Key Points ===");54    System.out.println("""55        1. Extend RuntimeException for unchecked56        2. Extend Exception for checked57        3. Call super(message) to set message58        4. Name should end with 'Exception'59        5. Name should describe the error clearly60        """);61}
    output
    === Key Points ===
    1. Extend RuntimeException for unchecked
    2. Extend Exception for checked
    3. Call super(message) to set message
    4. Name should end with 'Exception'
    5. Name should describe the error clearly

Extend RuntimeException (unchecked) or Exception (checked).

custom exception Your own exception class. Meaningful name, specific to your domain.

Exception with data

Include relevant data in the exception.

transferAmount
ExceptionWithData.java
Replay: real traced execution (multi-file project)
// Custom Exceptions with Additional Data

public class ExceptionWithData {
    public static void main(String[] args) {
        System.out.println("=== Custom Exceptions with Data ===\n");

        // Exception with numeric data
        System.out.println("--- Exception with Numeric Data ---");

        double transferAmount = 150.00;
        try {
            transferMoney(100.00, transferAmount);
        } catch (InsufficientFundsException e) {
            System.out.println("Transfer failed!");
            System.out.println("  Message: " + e.getMessage());
            System.out.println("  Balance: $" + e.getBalance());
            System.out.println("  Requested: $" + e.getRequestedAmount());
            System.out.println("  Shortfall: $" + e.getShortfall());
        }

        // Exception with string data
        System.out.println("\n--- Exception with String Data ---");

        try {
            findUser("unknown_user");
        } catch (UserNotFoundException e) {
            System.out.println("User lookup failed!");
            System.out.println("  Message: " + e.getMessage());
            System.out.println("  Username: " + e.getUsername());
            System.out.println("  Search time: " + e.getSearchTimeMs() + "ms");
        }

        // Exception with enum data
        System.out.println("\n--- Exception with Enum Data ---");

        try {
            validateInput("", "email");
        } catch (ValidationException e) {
            System.out.println("Validation failed!");
            System.out.println("  Message: " + e.getMessage());
            System.out.println("  Field: " + e.getFieldName());
            System.out.println("  Error type: " + e.getErrorType());
            System.out.println("  Is required error? " + e.isRequiredError());
        }

        // Using exception data for recovery
        System.out.println("\n--- Using Data for Recovery ---");

        double balance = 100.00;
        double[] amounts = {50.00, 75.00, 30.00};

        for (double amount : amounts) {
            try {
                withdraw(balance, amount);
                balance -= amount;
                System.out.println("✓ Withdrew $" + amount + ", new balance: $" + balance);
            } catch (InsufficientFundsException e) {
                System.out.println("✗ Cannot withdraw $" + amount);
                System.out.println("  Suggestion: withdraw up to $" + e.getBalance());
            }
        }

        System.out.println("\n=== Key Points ===");
        System.out.println("""
            1. Add fields to store relevant error data
            2. Provide getters to access the data
            3. Include data in the message for readability
            4. Caller can use data for error recovery
            5. Consider computed properties (like shortfall)
            """);
    }

    static void transferMoney(double balance, double amount) {
        if (amount > balance) {
            throw new InsufficientFundsException(balance, amount);
        }
        System.out.println("Transfer of $" + amount + " successful");
    }

    static void withdraw(double balance, double amount) {
        if (amount > balance) {
            throw new InsufficientFundsException(balance, amount);
        }
    }

    static void findUser(String username) {
        // Simulate search with a fixed duration
        long searchTime = 50;

        // User not found
        throw new UserNotFoundException(username, searchTime);
    }

    static void validateInput(String value, String fieldName) {
        if (value == null || value.isBlank()) {
            throw new ValidationException(fieldName, ValidationErrorType.REQUIRED);
        }
    }
}

// Exception with numeric data
class InsufficientFundsException extends RuntimeException {
    private final double balance;
    private final double requestedAmount;

    public InsufficientFundsException(double balance, double requestedAmount) {
        super(String.format(
            "Insufficient funds: balance=$%.2f, requested=$%.2f",
            balance, requestedAmount
        ));
        this.balance = balance;
        this.requestedAmount = requestedAmount;
    }

    public double getBalance() {
        return balance;
    }

    public double getRequestedAmount() {
        return requestedAmount;
    }

    public double getShortfall() {
        return requestedAmount - balance;
    }
}

// Exception with string data
class UserNotFoundException extends RuntimeException {
    private final String username;
    private final long searchTimeMs;

    public UserNotFoundException(String username, long searchTimeMs) {
        super("User not found: " + username);
        this.username = username;
        this.searchTimeMs = searchTimeMs;
    }

    public String getUsername() {
        return username;
    }

    public long getSearchTimeMs() {
        return searchTimeMs;
    }
}

// Validation error type enum
enum ValidationErrorType {
    REQUIRED,
    INVALID_FORMAT,
    TOO_SHORT,
    TOO_LONG
}

// Exception with enum data
class ValidationException extends RuntimeException {
    private final String fieldName;
    private final ValidationErrorType errorType;

    public ValidationException(String fieldName, ValidationErrorType errorType) {
        super(fieldName + ": " + errorType.name().toLowerCase().replace('_', ' '));
        this.fieldName = fieldName;
        this.errorType = errorType;
    }

    public String getFieldName() {
        return fieldName;
    }

    public ValidationErrorType getErrorType() {
        return errorType;
    }

    public boolean isRequiredError() {
        return errorType == ValidationErrorType.REQUIRED;
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// Custom Exceptions with Additional Data

public class ExceptionWithData {
    public static void main(String[] args) {
        System.out.println("=== Custom Exceptions with Data ===\n");

        // Exception with numeric data
        System.out.println("--- Exception with Numeric Data ---");

        double transferAmount = 75.00;
        try {
            transferMoney(100.00, transferAmount);
        } catch (InsufficientFundsException e) {
            System.out.println("Transfer failed!");
            System.out.println("  Message: " + e.getMessage());
            System.out.println("  Balance: $" + e.getBalance());
            System.out.println("  Requested: $" + e.getRequestedAmount());
            System.out.println("  Shortfall: $" + e.getShortfall());
        }

        // Exception with string data
        System.out.println("\n--- Exception with String Data ---");

        try {
            findUser("unknown_user");
        } catch (UserNotFoundException e) {
            System.out.println("User lookup failed!");
            System.out.println("  Message: " + e.getMessage());
            System.out.println("  Username: " + e.getUsername());
            System.out.println("  Search time: " + e.getSearchTimeMs() + "ms");
        }

        // Exception with enum data
        System.out.println("\n--- Exception with Enum Data ---");

        try {
            validateInput("", "email");
        } catch (ValidationException e) {
            System.out.println("Validation failed!");
            System.out.println("  Message: " + e.getMessage());
            System.out.println("  Field: " + e.getFieldName());
            System.out.println("  Error type: " + e.getErrorType());
            System.out.println("  Is required error? " + e.isRequiredError());
        }

        // Using exception data for recovery
        System.out.println("\n--- Using Data for Recovery ---");

        double balance = 100.00;
        double[] amounts = {50.00, 75.00, 30.00};

        for (double amount : amounts) {
            try {
                withdraw(balance, amount);
                balance -= amount;
                System.out.println("✓ Withdrew $" + amount + ", new balance: $" + balance);
            } catch (InsufficientFundsException e) {
                System.out.println("✗ Cannot withdraw $" + amount);
                System.out.println("  Suggestion: withdraw up to $" + e.getBalance());
            }
        }

        System.out.println("\n=== Key Points ===");
        System.out.println("""
            1. Add fields to store relevant error data
            2. Provide getters to access the data
            3. Include data in the message for readability
            4. Caller can use data for error recovery
            5. Consider computed properties (like shortfall)
            """);
    }

    static void transferMoney(double balance, double amount) {
        if (amount > balance) {
            throw new InsufficientFundsException(balance, amount);
        }
        System.out.println("Transfer of $" + amount + " successful");
    }

    static void withdraw(double balance, double amount) {
        if (amount > balance) {
            throw new InsufficientFundsException(balance, amount);
        }
    }

    static void findUser(String username) {
        // Simulate search with a fixed duration
        long searchTime = 50;

        // User not found
        throw new UserNotFoundException(username, searchTime);
    }

    static void validateInput(String value, String fieldName) {
        if (value == null || value.isBlank()) {
            throw new ValidationException(fieldName, ValidationErrorType.REQUIRED);
        }
    }
}

// Exception with numeric data
class InsufficientFundsException extends RuntimeException {
    private final double balance;
    private final double requestedAmount;

    public InsufficientFundsException(double balance, double requestedAmount) {
        super(String.format(
            "Insufficient funds: balance=$%.2f, requested=$%.2f",
            balance, requestedAmount
        ));
        this.balance = balance;
        this.requestedAmount = requestedAmount;
    }

    public double getBalance() {
        return balance;
    }

    public double getRequestedAmount() {
        return requestedAmount;
    }

    public double getShortfall() {
        return requestedAmount - balance;
    }
}

// Exception with string data
class UserNotFoundException extends RuntimeException {
    private final String username;
    private final long searchTimeMs;

    public UserNotFoundException(String username, long searchTimeMs) {
        super("User not found: " + username);
        this.username = username;
        this.searchTimeMs = searchTimeMs;
    }

    public String getUsername() {
        return username;
    }

    public long getSearchTimeMs() {
        return searchTimeMs;
    }
}

// Validation error type enum
enum ValidationErrorType {
    REQUIRED,
    INVALID_FORMAT,
    TOO_SHORT,
    TOO_LONG
}

// Exception with enum data
class ValidationException extends RuntimeException {
    private final String fieldName;
    private final ValidationErrorType errorType;

    public ValidationException(String fieldName, ValidationErrorType errorType) {
        super(fieldName + ": " + errorType.name().toLowerCase().replace('_', ' '));
        this.fieldName = fieldName;
        this.errorType = errorType;
    }

    public String getFieldName() {
        return fieldName;
    }

    public ValidationErrorType getErrorType() {
        return errorType;
    }

    public boolean isRequiredError() {
        return errorType == ValidationErrorType.REQUIRED;
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
  1. transferAmount ← 150.0

    3public class ExceptionWithData {4    public static void main(String[] args) {5        System.out.println("=== Custom Exceptions with Data ===\n");67        // Exception with numeric data //?numeric_data8        System.out.println("--- Exception with Numeric Data ---");910        double transferAmount→ 150.0 = 150.00;  //@transferAmount=150.00, 75.0011        try { //?try_transfer
    output=== Custom Exceptions with Data ===
    --- Exception with Numeric Data ---
  2. try

    10double transferAmount = 150.00;  //@transferAmount=150.00, 75.0011try { //?try_transfer12    transferMoney(100.00, transferAmount150.0); //?call_transfer13} catch (InsufficientFundsException e) { //?catch_transfer
  3. static void transferMoney(double balance, double amount)

    73static void transferMoney(double balance100.0, double amount150.0) { //?transfer_method74    if (amount > balance) { //?check_funds
  4. if (amount > balance)

    73static void transferMoney(double balance, double amount) { //?transfer_method74    if (amount150.0 > balance100.0) { //?check_funds75        throw new InsufficientFundsException(balance, amount); //?throw_insufficient76    }
  5. this.balance ← 100.0, this.requestedAmount ← 150.0

    pass 1 of 2
    106public InsufficientFundsException(double balance100.0, double requestedAmount150.0) { //?insufficient_constructor107    super(String.format( //?format_message108        "Insufficient funds: balance=$%.2f, requested=$%.2f",109        balance, requestedAmount110    ));111    this.balance→ 100.0 = balance100.0; //?set_balance112    this.requestedAmount→ 150.0 = requestedAmount150.0; //?set_requested113}
  6. catch (InsufficientFundsException e)

    12    transferMoney(100.00, transferAmount); //?call_transfer13} catch (InsufficientFundsException eInsufficientFundsException: Insufficient funds: balance=$100.00, requested=$150.00) { //?catch_transfer14    System.out.println("Transfer failed!"); //?print_failed15    System.out.println("  Message: " + e.getMessage()); //?print_message16    System.out.println("  Balance: $" + e.getBalance()); //?print_balance17    System.out.println("  Requested: $" + e.getRequestedAmount()); //?print_requested
    outputTransfer failed!
      Message: Insufficient funds: balance=$100.00, requested=$150.00
  7. public double getBalance()

    pass 1 of 2
    115public double getBalance() { //?get_balance116    return balance100.0;117}
  8. System.out.println(" Balance: $" + e.getBalance()); //?print_balance

    15System.out.println("  Message: " + e.getMessage()); //?print_message16System.out.println("  Balance: $" + e.getBalance()); //?print_balance17System.out.println("  Requested: $" + e.getRequestedAmount()); //?print_requested18System.out.println("  Shortfall: $" + e.getShortfall()); //?print_shortfall
    output  Balance: $100.0
  9. public double getRequestedAmount()

    119public double getRequestedAmount() { //?get_requested120    return requestedAmount150.0;121}
  10. System.out.println(" Requested: $" + e.getRequestedAmount()); //?prin…

    16    System.out.println("  Balance: $" + e.getBalance()); //?print_balance17    System.out.println("  Requested: $" + e.getRequestedAmount()); //?print_requested18    System.out.println("  Shortfall: $" + e.getShortfall()); //?print_shortfall19}
    output  Requested: $150.0
  11. public double getShortfall()

    123public double getShortfall() { //?get_shortfall124    return requestedAmount150.0 - balance100.0; //?calc_shortfall125}
  12. System.out.println(" Shortfall: $" + e.getShortfall()); //?print_shor…

    17    System.out.println("  Requested: $" + e.getRequestedAmount()); //?print_requested18    System.out.println("  Shortfall: $" + e.getShortfall()); //?print_shortfall19}2021// Exception with string data //?string_data22System.out.println("\n--- Exception with String Data ---");
    output  Shortfall: $50.0
    
    --- Exception with String Data ---
  13. searchTime ← 50

    86static void findUser(String usernameunknown_user) { //?find_user_method87    // Simulate search with a fixed duration //?simulate_search88    long searchTime→ 50 = 50; //?calc_time8990    // User not found //?not_found91    throw new UserNotFoundException(username, searchTime); //?throw_not_found92}
  14. this.username ← unknown_user, this.searchTimeMs ← 50

    133public UserNotFoundException(String usernameunknown_user, long searchTimeMs50) { //?user_constructor134    super("User not found: " + username); //?user_message135    this.username→ unknown_user = usernameunknown_user; //?set_username136    this.searchTimeMs→ 50 = searchTimeMs50; //?set_search_time137}
  15. catch (UserNotFoundException e)

    25    findUser("unknown_user"); //?call_find_user26} catch (UserNotFoundException eUserNotFoundException: User not found: unknown_user) { //?catch_user27    System.out.println("User lookup failed!"); //?print_user_failed28    System.out.println("  Message: " + e.getMessage()); //?print_user_message29    System.out.println("  Username: " + e.getUsername()); //?print_username30    System.out.println("  Search time: " + e.getSearchTimeMs() + "ms"); //?print_search_time
    outputUser lookup failed!
      Message: User not found: unknown_user
  16. public String getUsername()

    139public String getUsername() { //?get_username140    return usernameunknown_user;141}
  17. System.out.println(" Username: " + e.getUsername()); //?print_usernam…

    28    System.out.println("  Message: " + e.getMessage()); //?print_user_message29    System.out.println("  Username: " + e.getUsername()); //?print_username30    System.out.println("  Search time: " + e.getSearchTimeMs() + "ms"); //?print_search_time31}
    output  Username: unknown_user
  18. public long getSearchTimeMs()

    143public long getSearchTimeMs() { //?get_search_time144    return searchTimeMs50;145}
  19. System.out.println(" Search time: " + e.getSearchTimeMs() + "ms"); //…

    29    System.out.println("  Username: " + e.getUsername()); //?print_username30    System.out.println("  Search time: " + e.getSearchTimeMs() + "ms"); //?print_search_time31}3233// Exception with enum data //?enum_data34System.out.println("\n--- Exception with Enum Data ---");
    output  Search time: 50ms
    
    --- Exception with Enum Data ---
  20. static void validateInput(String value, String fieldName)

    94static void validateInput(String value(empty), String fieldNameemail) { //?validate_input_method95    if (value == null || value.isBlank()) { //?check_blank
  21. if (value == null || value.isBlank())

    94static void validateInput(String value, String fieldName) { //?validate_input_method95    if (value(empty) == null || value.isBlank()) { //?check_blank96        throw new ValidationException(fieldName, ValidationErrorType.REQUIRED); //?throw_required97    }
  22. this.fieldName ← email, this.errorType ← REQUIRED

    161public ValidationException(String fieldNameemail, ValidationErrorType errorTypeREQUIRED) { //?validation_constructor162    super(fieldName + ": " + errorType.name().toLowerCase().replace('_', ' ')); //?validation_message163    this.fieldName→ email = fieldNameemail; //?set_field_name164    this.errorType→ REQUIRED = errorTypeREQUIRED; //?set_error_type165}
  23. catch (ValidationException e)

    37    validateInput("", "email"); //?call_validate_input38} catch (ValidationException eValidationException: email: required) { //?catch_validate39    System.out.println("Validation failed!"); //?print_validate_failed40    System.out.println("  Message: " + e.getMessage()); //?print_validate_message41    System.out.println("  Field: " + e.getFieldName()); //?print_field42    System.out.println("  Error type: " + e.getErrorType()); //?print_error_type
    outputValidation failed!
      Message: email: required
  24. public String getFieldName()

    167public String getFieldName() { //?get_field_name168    return fieldNameemail;169}
  25. System.out.println(" Field: " + e.getFieldName()); //?print_field

    40System.out.println("  Message: " + e.getMessage()); //?print_validate_message41System.out.println("  Field: " + e.getFieldName()); //?print_field42System.out.println("  Error type: " + e.getErrorType()); //?print_error_type43System.out.println("  Is required error? " + e.isRequiredError()); //?print_is_required
    output  Field: email
  26. public ValidationErrorType getErrorType()

    171public ValidationErrorType getErrorType() { //?get_error_type172    return errorTypeREQUIRED;173}
  27. System.out.println(" Error type: " + e.getErrorType()); //?print_erro…

    41    System.out.println("  Field: " + e.getFieldName()); //?print_field42    System.out.println("  Error type: " + e.getErrorType()); //?print_error_type43    System.out.println("  Is required error? " + e.isRequiredError()); //?print_is_required44}
    output  Error type: REQUIRED
  28. public boolean isRequiredError()

    175public boolean isRequiredError() { //?is_required176    return errorTypeREQUIRED == ValidationErrorType.REQUIRED; //?check_required177}
  29. balance ← 100.0

    42    System.out.println("  Error type: " + e.getErrorType()); //?print_error_type43    System.out.println("  Is required error? " + e.isRequiredError()); //?print_is_required44}4546// Using exception data for recovery //?recovery47System.out.println("\n--- Using Data for Recovery ---");4849double balance→ 100.0 = 100.00; //?initial_balance50double[] amounts = {50.00, 75.00, 30.00}; //?amounts
    output  Is required error? true
    
    --- Using Data for Recovery ---
  30. for (double amount : amounts)

    pass 1 of 3
    52for (double amount50.0 : amounts) { //?loop_amounts53    try { //?try_withdraw
    All 3 passes — pass 1 is the card above
    passamountbalancerequestedAmountethis.balancethis.requestedAmount
    150.0
    275.050.075.0InsufficientFundsException: Insufficient funds: balance=$50.00, requested=$75.0050.075.0
    330.0
  31. try

    pass 1 of 3
    52for (double amount : amounts) { //?loop_amounts53    try { //?try_withdraw54        withdraw(balance100.0, amount50.0); //?call_withdraw55        balance -= amount; //?update_balance
    All 3 passes — pass 1 is the card above
    passbalanceamountrequestedAmountethis.balancethis.requestedAmount
    1100.050.0
    250.075.075.0InsufficientFundsException: Insufficient funds: balance=$50.00, requested=$75.0050.075.0
    350.030.0
  32. balance ← 50.0

    pass 1 of 3
    53        try { //?try_withdraw54            withdraw(balance100.0, amount50.0); //?call_withdraw55            balance→ 50.0 -= amount50.0; //?update_balance56            System.out.println("✓ Withdrew $" + amount50.0 + ", new balance: $" + balance50.0); //?print_success57        } catch (InsufficientFundsException e) { //?catch_withdraw58            System.out.println("✗ Cannot withdraw $" + amount); //?print_cannot59            System.out.println("  Suggestion: withdraw up to $" + e.getBalance()); //?print_suggestion60        }61    }6263    System.out.println("\n=== Key Points ===");64    System.out.println("""65        1. Add fields to store relevant error data66        2. Provide getters to access the data67        3. Include data in the message for readability68        4. Caller can use data for error recovery69        5. Consider computed properties (like shortfall)70        """);71}7273static void transferMoney(double balance, double amount) { //?transfer_method74    if (amount > balance) { //?check_funds75        throw new InsufficientFundsException(balance, amount); //?throw_insufficient76    }77    System.out.println("Transfer of $" + amount + " successful"); //?transfer_success78}7980static void withdraw(double balance100.0, double amount50.0) { //?withdraw_method81    if (amount > balance) { //?check_withdraw
    output✓ Withdrew $50.0, new balance: $50.0
    All 3 passes — pass 1 is the card above
    passamountrequestedAmountebalancethis.balancethis.requestedAmount
    150.0100.0 50.0
    275.075.0InsufficientFundsException: Insufficient funds: balance=$50.00, requested=$75.0050.050.075.0
    330.050.0 20.0
  33. if (amount > balance)

    80static void withdraw(double balance, double amount) { //?withdraw_method81    if (amount75.0 > balance50.0) { //?check_withdraw82        throw new InsufficientFundsException(balance, amount); //?throw_withdraw83    }
  34. this.balance ← 50.0, this.requestedAmount ← 75.0

    pass 2 of 2
    106public InsufficientFundsException(double balance50.0, double requestedAmount75.0) { //?insufficient_constructor107    super(String.format( //?format_message108        "Insufficient funds: balance=$%.2f, requested=$%.2f",109        balance, requestedAmount110    ));111    this.balance→ 50.0 = balance50.0; //?set_balance112    this.requestedAmount→ 75.0 = requestedAmount75.0; //?set_requested113}
  35. catch (InsufficientFundsException e)

    56    System.out.println("✓ Withdrew $" + amount + ", new balance: $" + balance); //?print_success57} catch (InsufficientFundsException eInsufficientFundsException: Insufficient funds: balance=$50.00, requested=$75.00) { //?catch_withdraw58    System.out.println("✗ Cannot withdraw $" + amount75.0); //?print_cannot59    System.out.println("  Suggestion: withdraw up to $" + e.getBalance()); //?print_suggestion60}
    output✗ Cannot withdraw $75.0
  36. public double getBalance()

    pass 2 of 2
    115public double getBalance() { //?get_balance116    return balance50.0;117}
  37. System.out.println(" Suggestion: withdraw up to $" + e.getBalance());…

    58    System.out.println("✗ Cannot withdraw $" + amount); //?print_cannot59    System.out.println("  Suggestion: withdraw up to $" + e.getBalance()); //?print_suggestion60}
    output  Suggestion: withdraw up to $50.0
  1. transferAmount ← 75.0

    3public class ExceptionWithData {4    public static void main(String[] args) {5        System.out.println("=== Custom Exceptions with Data ===\n");67        // Exception with numeric data8        System.out.println("--- Exception with Numeric Data ---");910        double transferAmount→ 75.0 = 75.00;11        try {
    output=== Custom Exceptions with Data ===
    --- Exception with Numeric Data ---
  2. try

    10double transferAmount = 75.00;11try {12    transferMoney(100.00, transferAmount75.0);13} catch (InsufficientFundsException e) {
  3. static void transferMoney(double balance, double amount)

    11    try {12        transferMoney(100.00, transferAmount75.0);13    } catch (InsufficientFundsException e) {14        System.out.println("Transfer failed!");15        System.out.println("  Message: " + e.getMessage());16        System.out.println("  Balance: $" + e.getBalance());17        System.out.println("  Requested: $" + e.getRequestedAmount());18        System.out.println("  Shortfall: $" + e.getShortfall());19    }2021    // Exception with string data22    System.out.println("\n--- Exception with String Data ---");2324    try {25        findUser("unknown_user");26    } catch (UserNotFoundException e) {27        System.out.println("User lookup failed!");28        System.out.println("  Message: " + e.getMessage());29        System.out.println("  Username: " + e.getUsername());30        System.out.println("  Search time: " + e.getSearchTimeMs() + "ms");31    }3233    // Exception with enum data34    System.out.println("\n--- Exception with Enum Data ---");3536    try {37        validateInput("", "email");38    } catch (ValidationException e) {39        System.out.println("Validation failed!");40        System.out.println("  Message: " + e.getMessage());41        System.out.println("  Field: " + e.getFieldName());42        System.out.println("  Error type: " + e.getErrorType());43        System.out.println("  Is required error? " + e.isRequiredError());44    }4546    // Using exception data for recovery47    System.out.println("\n--- Using Data for Recovery ---");4849    double balance = 100.00;50    double[] amounts = {50.00, 75.00, 30.00};5152    for (double amount : amounts) {53        try {54            withdraw(balance, amount);55            balance -= amount;56            System.out.println("✓ Withdrew $" + amount + ", new balance: $" + balance);57        } catch (InsufficientFundsException e) {58            System.out.println("✗ Cannot withdraw $" + amount);59            System.out.println("  Suggestion: withdraw up to $" + e.getBalance());60        }61    }6263    System.out.println("\n=== Key Points ===");64    System.out.println("""65        1. Add fields to store relevant error data66        2. Provide getters to access the data67        3. Include data in the message for readability68        4. Caller can use data for error recovery69        5. Consider computed properties (like shortfall)70        """);71}7273static void transferMoney(double balance100.0, double amount75.0) {74    if (amount > balance) {75        throw new InsufficientFundsException(balance, amount);76    }77    System.out.println("Transfer of $" + amount75.0 + " successful");78}
    outputTransfer of $75.0 successful
    
    --- Exception with String Data ---
  4. searchTime ← 50

    86static void findUser(String usernameunknown_user) {87    // Simulate search with a fixed duration88    long searchTime→ 50 = 50;8990    // User not found91    throw new UserNotFoundException(username, searchTime);92}
  5. this.username ← unknown_user, this.searchTimeMs ← 50

    133public UserNotFoundException(String usernameunknown_user, long searchTimeMs50) {134    super("User not found: " + username);135    this.username→ unknown_user = usernameunknown_user;136    this.searchTimeMs→ 50 = searchTimeMs50;137}
  6. catch (UserNotFoundException e)

    25    findUser("unknown_user");26} catch (UserNotFoundException eUserNotFoundException: User not found: unknown_user) {27    System.out.println("User lookup failed!");28    System.out.println("  Message: " + e.getMessage());29    System.out.println("  Username: " + e.getUsername());30    System.out.println("  Search time: " + e.getSearchTimeMs() + "ms");
    outputUser lookup failed!
      Message: User not found: unknown_user
  7. public String getUsername()

    139public String getUsername() {140    return usernameunknown_user;141}
  8. System.out.println(" Username: " + e.getUsername());

    28    System.out.println("  Message: " + e.getMessage());29    System.out.println("  Username: " + e.getUsername());30    System.out.println("  Search time: " + e.getSearchTimeMs() + "ms");31}
    output  Username: unknown_user
  9. public long getSearchTimeMs()

    143public long getSearchTimeMs() {144    return searchTimeMs50;145}
  10. System.out.println(" Search time: " + e.getSearchTimeMs() + "ms");

    29    System.out.println("  Username: " + e.getUsername());30    System.out.println("  Search time: " + e.getSearchTimeMs() + "ms");31}3233// Exception with enum data34System.out.println("\n--- Exception with Enum Data ---");
    output  Search time: 50ms
    
    --- Exception with Enum Data ---
  11. static void validateInput(String value, String fieldName)

    94static void validateInput(String value(empty), String fieldNameemail) {95    if (value == null || value.isBlank()) {
  12. if (value == null || value.isBlank())

    94static void validateInput(String value, String fieldName) {95    if (value(empty) == null || value.isBlank()) {96        throw new ValidationException(fieldName, ValidationErrorType.REQUIRED);97    }
  13. this.fieldName ← email, this.errorType ← REQUIRED

    161public ValidationException(String fieldNameemail, ValidationErrorType errorTypeREQUIRED) {162    super(fieldName + ": " + errorType.name().toLowerCase().replace('_', ' '));163    this.fieldName→ email = fieldNameemail;164    this.errorType→ REQUIRED = errorTypeREQUIRED;165}
  14. catch (ValidationException e)

    37    validateInput("", "email");38} catch (ValidationException eValidationException: email: required) {39    System.out.println("Validation failed!");40    System.out.println("  Message: " + e.getMessage());41    System.out.println("  Field: " + e.getFieldName());42    System.out.println("  Error type: " + e.getErrorType());
    outputValidation failed!
      Message: email: required
  15. public String getFieldName()

    167public String getFieldName() {168    return fieldNameemail;169}
  16. System.out.println(" Field: " + e.getFieldName());

    40System.out.println("  Message: " + e.getMessage());41System.out.println("  Field: " + e.getFieldName());42System.out.println("  Error type: " + e.getErrorType());43System.out.println("  Is required error? " + e.isRequiredError());
    output  Field: email
  17. public ValidationErrorType getErrorType()

    171public ValidationErrorType getErrorType() {172    return errorTypeREQUIRED;173}
  18. System.out.println(" Error type: " + e.getErrorType());

    41    System.out.println("  Field: " + e.getFieldName());42    System.out.println("  Error type: " + e.getErrorType());43    System.out.println("  Is required error? " + e.isRequiredError());44}
    output  Error type: REQUIRED
  19. public boolean isRequiredError()

    175public boolean isRequiredError() {176    return errorTypeREQUIRED == ValidationErrorType.REQUIRED;177}
  20. balance ← 100.0

    42    System.out.println("  Error type: " + e.getErrorType());43    System.out.println("  Is required error? " + e.isRequiredError());44}4546// Using exception data for recovery47System.out.println("\n--- Using Data for Recovery ---");4849double balance→ 100.0 = 100.00;50double[] amounts = {50.00, 75.00, 30.00};
    output  Is required error? true
    
    --- Using Data for Recovery ---
  21. for (double amount : amounts)

    pass 1 of 3
    52for (double amount50.0 : amounts) {53    try {
    All 3 passes — pass 1 is the card above
    passamountbalancerequestedAmountethis.balancethis.requestedAmount
    150.0
    275.050.075.0InsufficientFundsException: Insufficient funds: balance=$50.00, requested=$75.0050.075.0
    330.0
  22. try

    pass 1 of 3
    52for (double amount : amounts) {53    try {54        withdraw(balance100.0, amount50.0);55        balance -= amount;
    All 3 passes — pass 1 is the card above
    passbalanceamountrequestedAmountethis.balancethis.requestedAmount
    1100.050.0
    250.075.075.0InsufficientFundsException: Insufficient funds: balance=$50.00, requested=$75.0050.075.0
    350.030.0
  23. balance ← 50.0

    pass 1 of 3
    53        try {54            withdraw(balance100.0, amount50.0);55            balance→ 50.0 -= amount50.0;56            System.out.println("✓ Withdrew $" + amount50.0 + ", new balance: $" + balance50.0);57        } catch (InsufficientFundsException e) {58            System.out.println("✗ Cannot withdraw $" + amount);59            System.out.println("  Suggestion: withdraw up to $" + e.getBalance());60        }61    }6263    System.out.println("\n=== Key Points ===");64    System.out.println("""65        1. Add fields to store relevant error data66        2. Provide getters to access the data67        3. Include data in the message for readability68        4. Caller can use data for error recovery69        5. Consider computed properties (like shortfall)70        """);71}7273static void transferMoney(double balance, double amount) {74    if (amount > balance) {75        throw new InsufficientFundsException(balance, amount);76    }77    System.out.println("Transfer of $" + amount + " successful");78}7980static void withdraw(double balance100.0, double amount50.0) {81    if (amount > balance) {
    output✓ Withdrew $50.0, new balance: $50.0
    All 3 passes — pass 1 is the card above
    passamountrequestedAmountebalancethis.balancethis.requestedAmount
    150.0100.0 50.0
    275.075.0InsufficientFundsException: Insufficient funds: balance=$50.00, requested=$75.0050.050.075.0
    330.050.0 20.0
  24. if (amount > balance)

    80static void withdraw(double balance, double amount) {81    if (amount75.0 > balance50.0) {82        throw new InsufficientFundsException(balance, amount);83    }
  25. this.balance ← 50.0, this.requestedAmount ← 75.0

    106public InsufficientFundsException(double balance50.0, double requestedAmount75.0) {107    super(String.format(108        "Insufficient funds: balance=$%.2f, requested=$%.2f",109        balance, requestedAmount110    ));111    this.balance→ 50.0 = balance50.0;112    this.requestedAmount→ 75.0 = requestedAmount75.0;113}
  26. catch (InsufficientFundsException e)

    56    System.out.println("✓ Withdrew $" + amount + ", new balance: $" + balance);57} catch (InsufficientFundsException eInsufficientFundsException: Insufficient funds: balance=$50.00, requested=$75.00) {58    System.out.println("✗ Cannot withdraw $" + amount75.0);59    System.out.println("  Suggestion: withdraw up to $" + e.getBalance());60}
    output✗ Cannot withdraw $75.0
  27. public double getBalance()

    115public double getBalance() {116    return balance50.0;117}
  28. System.out.println(" Suggestion: withdraw up to $" + e.getBalance());

    58    System.out.println("✗ Cannot withdraw $" + amount);59    System.out.println("  Suggestion: withdraw up to $" + e.getBalance());60}
    output  Suggestion: withdraw up to $50.0

Add fields, constructor, getters. Carry context about what went wrong.

Exception hierarchy

Organize related exceptions into a hierarchy.

ExceptionHierarchy.java
Replay: real traced execution (multi-file project)
// Building an Exception Class Hierarchy

public class ExceptionHierarchy {
    public static void main(String[] args) {
        System.out.println("=== Exception Hierarchy ===\n");

        // Show the hierarchy
        System.out.println("--- Our Custom Hierarchy ---");
        System.out.println("""
            AppException (base)
            ├── DataException
            │   ├── DataNotFoundException
            │   └── DataValidationException
            └── ServiceException
                ├── AuthenticationException
                └── AuthorizationException
            """);

        // Catch specific exception
        System.out.println("--- Catch Specific Exception ---");

        try {
            findProduct(999);
        } catch (DataNotFoundException e) {
            System.out.println("Caught DataNotFoundException: " + e.getMessage());
            System.out.println("  Entity: " + e.getEntityType());
            System.out.println("  ID: " + e.getEntityId());
        }

        // Catch parent exception
        System.out.println("\n--- Catch Parent Exception (DataException) ---");

        try {
            validateProduct("", -10);
        } catch (DataException e) {
            System.out.println("Caught DataException: " + e.getClass().getSimpleName());
            System.out.println("  Message: " + e.getMessage());
            System.out.println("  Error Code: " + e.getErrorCode());
        }

        // Catch base exception
        System.out.println("\n--- Catch Base Exception (AppException) ---");

        String[] operations = {"find", "validate", "auth", "authz"};

        for (String op : operations) {
            try {
                performOperation(op);
            } catch (AppException e) {
                System.out.println("[" + e.getErrorCode() + "] " +
                    e.getClass().getSimpleName() + ": " + e.getMessage());
            }
        }

        // Check exception type
        System.out.println("\n--- Check Exception Type ---");

        try {
            performOperation("auth");
        } catch (AppException e) {
            if (e instanceof AuthenticationException) {
                System.out.println("Authentication failed - redirect to login");
            } else if (e instanceof AuthorizationException) {
                System.out.println("Not authorized - show error page");
            } else if (e instanceof DataException) {
                System.out.println("Data error - retry or notify user");
            }
        }

        System.out.println("\n=== Key Points ===");
        System.out.println("""
            1. Create a base exception for your application
            2. Group related exceptions under category classes
            3. Include common fields in base (like errorCode)
            4. Catch at appropriate level of specificity
            5. Use instanceof to check type when needed
            """);
    }

    static void findProduct(int id) {
        throw new DataNotFoundException("Product", id);
    }

    static void validateProduct(String name, double price) {
        if (name.isBlank()) {
            throw new DataValidationException("Product name is required");
        }
        if (price < 0) {
            throw new DataValidationException("Price cannot be negative");
        }
    }

    static void performOperation(String operation) {
        switch (operation) {
            case "find" -> throw new DataNotFoundException("User", 1);
            case "validate" -> throw new DataValidationException("Invalid data");
            case "auth" -> throw new AuthenticationException("Invalid credentials");
            case "authz" -> throw new AuthorizationException("admin", "DELETE");
        }
    }
}

// Base exception for the application
abstract class AppException extends RuntimeException {
    private final String errorCode;

    public AppException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public AppException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }
}

// Category: Data-related exceptions
abstract class DataException extends AppException {
    public DataException(String errorCode, String message) {
        super(errorCode, message);
    }
}

// Specific: Entity not found
class DataNotFoundException extends DataException {
    private final String entityType;
    private final Object entityId;

    public DataNotFoundException(String entityType, Object entityId) {
        super("NOT_FOUND", entityType + " not found with id: " + entityId);
        this.entityType = entityType;
        this.entityId = entityId;
    }

    public String getEntityType() { return entityType; }
    public Object getEntityId() { return entityId; }
}

// Specific: Validation error
class DataValidationException extends DataException {
    public DataValidationException(String message) {
        super("VALIDATION_ERROR", message);
    }
}

// Category: Service-related exceptions
abstract class ServiceException extends AppException {
    public ServiceException(String errorCode, String message) {
        super(errorCode, message);
    }
}

// Specific: Authentication failure
class AuthenticationException extends ServiceException {
    public AuthenticationException(String message) {
        super("AUTH_FAILED", message);
    }
}

// Specific: Authorization failure
class AuthorizationException extends ServiceException {
    private final String requiredRole;
    private final String action;

    public AuthorizationException(String requiredRole, String action) {
        super("ACCESS_DENIED",
            "Access denied: requires " + requiredRole + " role for " + action);
        this.requiredRole = requiredRole;
        this.action = action;
    }

    public String getRequiredRole() { return requiredRole; }
    public String getAction() { return action; }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
  1. public static void main(String[] args)

    3public class ExceptionHierarchy {4    public static void main(String[] args) {5        System.out.println("=== Exception Hierarchy ===\n");67        // Show the hierarchy //?show_hierarchy8        System.out.println("--- Our Custom Hierarchy ---");9        System.out.println("""10            AppException (base)11            ├── DataException12            │   ├── DataNotFoundException13            │   └── DataValidationException14            └── ServiceException15                ├── AuthenticationException16                └── AuthorizationException17            """);1819        // Catch specific exception //?catch_specific20        System.out.println("--- Catch Specific Exception ---");
    output=== Exception Hierarchy ===
    --- Our Custom Hierarchy ---
    AppException (base)
    ├── DataException
    │   ├── DataNotFoundException
    │   └── DataValidationException
    └── ServiceException
        ├── AuthenticationException
        └── AuthorizationException
    --- Catch Specific Exception ---
  2. static void findProduct(int id)

    80static void findProduct(int id999) { //?find_product_method81    throw new DataNotFoundException("Product", id); //?throw_not_found82}
  3. this.errorCode ← NOT_FOUND

    pass 1 of 7
    107public AppException(String errorCodeNOT_FOUND, String messageProduct not found with id: 999) { //?app_constructor108    super(message); //?super_message109    this.errorCode→ NOT_FOUND = errorCodeNOT_FOUND; //?set_error_code110}
    All 7 passes — pass 1 is the card above
    passerrorCodemessageentityTypeentityIderequiredRoleactionthis.errorCodethis.entityTypethis.entityIdthis.requiredRolethis.action
    1NOT_FOUNDProduct not found with id: 999Product999DataNotFoundException: Product not found with id: 999NOT_FOUNDProduct999
    2VALIDATION_ERRORProduct name is requiredDataValidationException: Product name is requiredVALIDATION_ERROR
    3NOT_FOUNDUser not found with id: 1User1NOT_FOUNDUser1
    4VALIDATION_ERRORInvalid dataVALIDATION_ERROR
    5AUTH_FAILEDInvalid credentialsAUTH_FAILED
    6ACCESS_DENIEDAccess denied: requires admin role for DELETEadminDELETEACCESS_DENIEDadminDELETE
    7AUTH_FAILEDInvalid credentialsAuthenticationException: Invalid credentialsAUTH_FAILED
  4. public DataException(String errorCode, String message)

    pass 1 of 4
    123abstract class DataException extends AppException { //?data_exception_def124    public DataException(String errorCodeNOT_FOUND, String messageProduct not found with id: 999) { //?data_constructor125        super(errorCode, message); //?data_super
    All 4 passes — pass 1 is the card above
    passerrorCodemessageentityTypeentityIdethis.entityTypethis.entityId
    1NOT_FOUNDProduct not found with id: 999Product999DataNotFoundException: Product not found with id: 999Product999
    2VALIDATION_ERRORProduct name is requiredDataValidationException: Product name is required
    3NOT_FOUNDUser not found with id: 1User1User1
    4VALIDATION_ERRORInvalid data
  5. this.entityType ← Product, this.entityId ← 999

    pass 1 of 2
    134public DataNotFoundException(String entityTypeProduct, Object entityId999) { //?not_found_constructor135    super("NOT_FOUND", entityType + " not found with id: " + entityId); //?not_found_super136    this.entityType→ Product = entityTypeProduct; //?set_entity_type137    this.entityId→ 999 = entityId999; //?set_entity_id138}
  6. catch (DataNotFoundException e)

    23    findProduct(999); //?call_find_product24} catch (DataNotFoundException eDataNotFoundException: Product not found with id: 999) { //?catch_not_found25    System.out.println("Caught DataNotFoundException: " + e.getMessage()); //?print_not_found26    System.out.println("  Entity: " + e.getEntityType()); //?print_entity27    System.out.println("  ID: " + e.getEntityId()); //?print_id
    outputCaught DataNotFoundException: Product not found with id: 999
  7. public String getEntityType()

    140public String getEntityType() { return entityTypeProduct; } //?get_entity_type141public Object getEntityId() { return entityId; } //?get_entity_id
  8. System.out.println(" Entity: " + e.getEntityType()); //?print_entity

    25    System.out.println("Caught DataNotFoundException: " + e.getMessage()); //?print_not_found26    System.out.println("  Entity: " + e.getEntityType()); //?print_entity27    System.out.println("  ID: " + e.getEntityId()); //?print_id28}
    output  Entity: Product
  9. public Object getEntityId()

    140    public String getEntityType() { return entityType; } //?get_entity_type141    public Object getEntityId() { return entityId999; } //?get_entity_id142}
  10. System.out.println(" ID: " + e.getEntityId()); //?print_id

    26    System.out.println("  Entity: " + e.getEntityType()); //?print_entity27    System.out.println("  ID: " + e.getEntityId()); //?print_id28}2930// Catch parent exception //?catch_parent31System.out.println("\n--- Catch Parent Exception (DataException) ---");
    output  ID: 999
    
    --- Catch Parent Exception (DataException) ---
  11. static void validateProduct(String name, double price)

    84static void validateProduct(String name(empty), double price-10.0) { //?validate_product_method85    if (name.isBlank()) { //?check_name
  12. public DataValidationException(String message)

    pass 1 of 2
    145class DataValidationException extends DataException { //?validation_def146    public DataValidationException(String messageProduct name is required) { //?validation_constructor147        super("VALIDATION_ERROR", message); //?validation_super
  13. catch (DataException e)

    34    validateProduct("", -10); //?call_validate35} catch (DataException eDataValidationException: Product name is required) { //?catch_data_exception36    System.out.println("Caught DataException: " + e.getClass().getSimpleName()); //?print_class37    System.out.println("  Message: " + e.getMessage()); //?print_parent_msg38    System.out.println("  Error Code: " + e.getErrorCode()); //?print_error_code39}
    outputCaught DataException: DataValidationException
      Message: Product name is required
  14. public String getErrorCode()

    pass 1 of 5
    117public String getErrorCode() { //?get_error_code118    return errorCodeVALIDATION_ERROR;119}
    All 5 passes — pass 1 is the card above
    passerrorCode
    1VALIDATION_ERROR
    2NOT_FOUND
    3VALIDATION_ERROR
    4AUTH_FAILED
    5ACCESS_DENIED
  15. String[] operations = {"find", "validate", "auth", "authz"}; //?operat…

    37    System.out.println("  Message: " + e.getMessage()); //?print_parent_msg38    System.out.println("  Error Code: " + e.getErrorCode()); //?print_error_code39}4041// Catch base exception //?catch_base42System.out.println("\n--- Catch Base Exception (AppException) ---");4344String[] operations = {"find", "validate", "auth", "authz"}; //?operations
    output  Error Code: VALIDATION_ERROR
    
    --- Catch Base Exception (AppException) ---
  16. for (String op : operations)

    pass 1 of 4
    46for (String opfind : operations) { //?loop_ops47    try { //?try_op
    All 4 passes — pass 1 is the card above
    passopentityTypeentityIdmessagerequiredRoleactionthis.entityTypethis.entityIdthis.requiredRolethis.action
    1findUser1User1
    2validateInvalid data
    3authInvalid credentials
    4authzadminDELETEadminDELETE
  17. try

    pass 1 of 4
    46for (String op : operations) { //?loop_ops47    try { //?try_op48        performOperation(opfind); //?call_perform49    } catch (AppException e) { //?catch_app
    All 4 passes — pass 1 is the card above
    passopentityTypeentityIdmessagerequiredRoleactionthis.entityTypethis.entityIdthis.requiredRolethis.action
    1findUser1User1
    2validateInvalid data
    3authInvalid credentials
    4authzadminDELETEadminDELETE
  18. static void performOperation(String operation)

    pass 1 of 5
    93static void performOperation(String operationfind) { //?perform_op_method94    switch (operation) { //?switch_op
    All 5 passes — pass 1 is the card above
    passoperationentityTypeentityIdmessagerequiredRoleactionethis.entityTypethis.entityIdthis.requiredRolethis.action
    1findUser1User1
    2validateInvalid data
    3authInvalid credentials
    4authzadminDELETEadminDELETE
    5authInvalid credentialsAuthenticationException: Invalid credentials
  19. this.entityType ← User, this.entityId ← 1

    pass 2 of 2
    134public DataNotFoundException(String entityTypeUser, Object entityId1) { //?not_found_constructor135    super("NOT_FOUND", entityType + " not found with id: " + entityId); //?not_found_super136    this.entityType→ User = entityTypeUser; //?set_entity_type137    this.entityId→ 1 = entityId1; //?set_entity_id138}
  20. catch (AppException e)

    pass 1 of 4
    48    performOperation(op); //?call_perform49} catch (AppException eDataNotFoundException: User not found with id: 1) { //?catch_app50    System.out.println("[" + e.getErrorCode() + "] " + //?print_app_error51        e.getClass().getSimpleName() + ": " + e.getMessage());52}
    All 4 passes — pass 1 is the card above
    passe
    1DataNotFoundException: User not found with id: 1
    2DataValidationException: Invalid data
    3AuthenticationException: Invalid credentials
    4AuthorizationException: Access denied: requires admin role for DELETE
  21. System.out.println("[" + e.getErrorCode() + "] " + //?print_app_error

    49} catch (AppException e) { //?catch_app50    System.out.println("[" + e.getErrorCode() + "] " + //?print_app_error51        e.getClass().getSimpleName() + ": " + e.getMessage());52}
    output[NOT_FOUND] DataNotFoundException: User not found with id: 1
  22. public DataValidationException(String message)

    pass 2 of 2
    145class DataValidationException extends DataException { //?validation_def146    public DataValidationException(String messageInvalid data) { //?validation_constructor147        super("VALIDATION_ERROR", message); //?validation_super
  23. System.out.println("[" + e.getErrorCode() + "] " + //?print_app_error

    49} catch (AppException e) { //?catch_app50    System.out.println("[" + e.getErrorCode() + "] " + //?print_app_error51        e.getClass().getSimpleName() + ": " + e.getMessage());52}
    output[VALIDATION_ERROR] DataValidationException: Invalid data
  24. public ServiceException(String errorCode, String message)

    pass 1 of 3
    152abstract class ServiceException extends AppException { //?service_exception_def153    public ServiceException(String errorCodeAUTH_FAILED, String messageInvalid credentials) { //?service_constructor154        super(errorCode, message); //?service_super
    All 3 passes — pass 1 is the card above
    passerrorCodemessagerequiredRoleactionethis.requiredRolethis.action
    1AUTH_FAILEDInvalid credentials
    2ACCESS_DENIEDAccess denied: requires admin role for DELETEadminDELETEadminDELETE
    3AUTH_FAILEDInvalid credentialsAuthenticationException: Invalid credentials
  25. public AuthenticationException(String message)

    pass 1 of 2
    159class AuthenticationException extends ServiceException { //?auth_def160    public AuthenticationException(String messageInvalid credentials) { //?auth_constructor161        super("AUTH_FAILED", message); //?auth_super
  26. System.out.println("[" + e.getErrorCode() + "] " + //?print_app_error

    49} catch (AppException e) { //?catch_app50    System.out.println("[" + e.getErrorCode() + "] " + //?print_app_error51        e.getClass().getSimpleName() + ": " + e.getMessage());52}
    output[AUTH_FAILED] AuthenticationException: Invalid credentials
  27. this.requiredRole ← admin, this.action ← DELETE

    170public AuthorizationException(String requiredRoleadmin, String actionDELETE) { //?authz_constructor171    super("ACCESS_DENIED", //?authz_super172        "Access denied: requires " + requiredRole + " role for " + action);173    this.requiredRole→ admin = requiredRoleadmin; //?set_required_role174    this.action→ DELETE = actionDELETE; //?set_action175}
  28. System.out.println("[" + e.getErrorCode() + "] " + //?print_app_error

    49    } catch (AppException e) { //?catch_app50        System.out.println("[" + e.getErrorCode() + "] " + //?print_app_error51            e.getClass().getSimpleName() + ": " + e.getMessage());52    }53}5455// Check exception type //?check_type56System.out.println("\n--- Check Exception Type ---");
    output[ACCESS_DENIED] AuthorizationException: Access denied: requires admin role for DELETE
    
    --- Check Exception Type ---
  29. public AuthenticationException(String message)

    pass 2 of 2
    159class AuthenticationException extends ServiceException { //?auth_def160    public AuthenticationException(String messageInvalid credentials) { //?auth_constructor161        super("AUTH_FAILED", message); //?auth_super
  30. catch (AppException e)

    59    performOperation("auth"); //?call_auth_op60} catch (AppException eAuthenticationException: Invalid credentials) { //?catch_check61    if (e instanceof AuthenticationException) { //?instanceof_auth
  31. if (e instanceof AuthenticationException)

    60} catch (AppException e) { //?catch_check61    if (e instanceof AuthenticationException) { //?instanceof_auth62        System.out.println("Authentication failed - redirect to login"); //?redirect_login63    } else if (e instanceof AuthorizationException) { //?instanceof_authz
    outputAuthentication failed - redirect to login
  32. System.out.println(" === Key Points ===");

    70    System.out.println("\n=== Key Points ===");71    System.out.println("""72        1. Create a base exception for your application73        2. Group related exceptions under category classes74        3. Include common fields in base (like errorCode)75        4. Catch at appropriate level of specificity76        5. Use instanceof to check type when needed77        """);78}
    output
    === Key Points ===
    1. Create a base exception for your application
    2. Group related exceptions under category classes
    3. Include common fields in base (like errorCode)
    4. Catch at appropriate level of specificity
    5. Use instanceof to check type when needed

Base exception class with specialized subclasses. Catch at any level.

Checked custom exception

Create exceptions that must be handled.

CheckedCustom.java
Replay: real traced execution (multi-file project)
// Creating Checked Custom Exceptions

import java.io.IOException;

public class CheckedCustom {
    public static void main(String[] args) {
        System.out.println("=== Checked Custom Exceptions ===\n");

        // Checked exception requires handling
        System.out.println("--- Checked Exception Requires Handling ---");

        try {
            loadConfiguration("app.properties");
        } catch (ConfigurationException e) {
            System.out.println("Configuration error!");
            System.out.println("  Message: " + e.getMessage());
            System.out.println("  File: " + e.getConfigFile());
            if (e.getCause() != null) {
                System.out.println("  Cause: " + e.getCause().getMessage());
            }
        }

        // Multiple checked exceptions
        System.out.println("\n--- Multiple Checked Exceptions ---");

        try {
            connectToService("db.example.com", 5432);
        } catch (ConnectionException e) {
            System.out.println("Connection failed!");
            System.out.println("  Host: " + e.getHost());
            System.out.println("  Port: " + e.getPort());
        } catch (ConfigurationException e) {
            System.out.println("Configuration error: " + e.getMessage());
        }

        // When to use checked vs unchecked
        System.out.println("\n--- When to Use Checked ---");
        System.out.println("""
            Use CHECKED custom exceptions when:
            • Caller can reasonably recover
            • External resource failure (files, network)
            • Caller should be forced to handle
            • Part of a public API contract

            Use UNCHECKED custom exceptions when:
            • Programming error (invalid arguments)
            • Unrecoverable situation
            • Caller typically cannot handle
            • Would clutter code with try-catch
            """);

        // Demonstrate forced handling
        System.out.println("--- Forced Handling Demo ---");

        processConfiguration();

        System.out.println("\n=== Key Points ===");
        System.out.println("""
            1. Extend Exception (not RuntimeException) for checked
            2. Caller MUST use try-catch or declare throws
            3. Good for recoverable external failures
            4. Forces caller to think about error handling
            5. Can still include additional data fields
            """);
    }

    // Method that throws checked exception
    static void loadConfiguration(String filename) throws ConfigurationException {
        // Simulate file not found
        throw new ConfigurationException(
            "Configuration file not found",
            filename,
            new IOException("File does not exist: " + filename)
        );
    }

    // Method that throws multiple checked exceptions
    static void connectToService(String host, int port)
            throws ConnectionException, ConfigurationException {
        if (host == null || host.isEmpty()) {
            throw new ConfigurationException("Host is required", "connection.properties");
        }
        // Simulate connection failure
        throw new ConnectionException(host, port, "Connection refused");
    }

    static void processConfiguration() {
        // Option 1: Handle with try-catch
        System.out.println("Option 1: Handle with try-catch");
        try {
            loadConfiguration("settings.xml");
        } catch (ConfigurationException e) {
            System.out.println("  Handled: " + e.getMessage());
        }

        // Option 2: Would be to declare 'throws'
        System.out.println("\nOption 2: Propagate with throws");
        System.out.println("  void myMethod() throws ConfigurationException { ... }");
    }
}

// Checked custom exception
class ConfigurationException extends Exception {
    private final String configFile;

    public ConfigurationException(String message, String configFile) {
        super(message);
        this.configFile = configFile;
    }

    public ConfigurationException(String message, String configFile, Throwable cause) {
        super(message, cause);
        this.configFile = configFile;
    }

    public String getConfigFile() {
        return configFile;
    }
}

// Another checked custom exception
class ConnectionException extends Exception {
    private final String host;
    private final int port;

    public ConnectionException(String host, int port, String message) {
        super(message);
        this.host = host;
        this.port = port;
    }

    public ConnectionException(String host, int port, String message, Throwable cause) {
        super(message, cause);
        this.host = host;
        this.port = port;
    }

    public String getHost() { return host; }
    public int getPort() { return port; }

    public String getConnectionString() {
        return host + ":" + port;
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
  1. public static void main(String[] args)

    5public class CheckedCustom {6    public static void main(String[] args) {7        System.out.println("=== Checked Custom Exceptions ===\n");89        // Checked exception requires handling //?checked_required10        System.out.println("--- Checked Exception Requires Handling ---");
    output=== Checked Custom Exceptions ===
    --- Checked Exception Requires Handling ---
  2. static void loadConfiguration(String filename) throws ConfigurationExc…

    pass 1 of 2
    67// Method that throws checked exception //?throws_checked68static void loadConfiguration(String filenameapp.properties) throws ConfigurationException { //?load_config_method69    // Simulate file not found //?simulate_not_found70    throw new ConfigurationException( //?throw_config71        "Configuration file not found",72        filename,73        new IOException("File does not exist: " + filename)74    );75}
  3. this.configFile ← app.properties

    pass 1 of 2
    111public ConfigurationException(String messageConfiguration file not found, String configFileapp.properties, Throwable causejava.io.IOException: File does not exist: app.properties) { //?config_constructor_cause112    super(message, cause); //?super_message_cause113    this.configFile→ app.properties = configFileapp.properties; //?set_config_file_cause114}
  4. catch (ConfigurationException e)

    13    loadConfiguration("app.properties"); //?call_load_config14} catch (ConfigurationException eConfigurationException: Configuration file not found) { //?catch_config15    System.out.println("Configuration error!"); //?print_config_error16    System.out.println("  Message: " + e.getMessage()); //?print_config_msg17    System.out.println("  File: " + e.getConfigFile()); //?print_config_file18    if (e.getCause() != null) { //?check_cause
    outputConfiguration error!
      Message: Configuration file not found
  5. public String getConfigFile()

    116public String getConfigFile() { //?get_config_file117    return configFileapp.properties;118}
  6. System.out.println(" File: " + e.getConfigFile()); //?print_config_fi…

    16System.out.println("  Message: " + e.getMessage()); //?print_config_msg17System.out.println("  File: " + e.getConfigFile()); //?print_config_file18if (e.getCause() != null) { //?check_cause
    output  File: app.properties
  7. if (e.getCause() != null)

    17System.out.println("  File: " + e.getConfigFile()); //?print_config_file18if (e.getCause() != null) { //?check_cause19    System.out.println("  Cause: " + e.getCause().getMessage()); //?print_cause20}
    output  Cause: File does not exist: app.properties
  8. System.out.println(" --- Multiple Checked Exceptions ---");

    23// Multiple checked exceptions //?multiple_checked24System.out.println("\n--- Multiple Checked Exceptions ---");
    output
    --- Multiple Checked Exceptions ---
  9. static void connectToService(String host, int port) //?connect_method …

    77// Method that throws multiple checked exceptions //?throws_multiple78static void connectToService(String hostdb.example.com, int port5432) //?connect_method79        throws ConnectionException, ConfigurationException { //?throws_clause80    if (host == null || host.isEmpty()) { //?check_host81        throw new ConfigurationException("Host is required", "connection.properties"); //?throw_host_error82    }83    // Simulate connection failure //?simulate_conn_fail84    throw new ConnectionException(host, port, "Connection refused"); //?throw_conn85}
  10. this.host ← db.example.com, this.port ← 5432

    126public ConnectionException(String hostdb.example.com, int port5432, String messageConnection refused) { //?connection_constructor127    super(message); //?conn_super128    this.host→ db.example.com = hostdb.example.com; //?set_host129    this.port→ 5432 = port5432; //?set_port130}
  11. catch (ConnectionException e)

    27    connectToService("db.example.com", 5432); //?call_connect28} catch (ConnectionException eConnectionException: Connection refused) { //?catch_connection29    System.out.println("Connection failed!"); //?print_conn_error30    System.out.println("  Host: " + e.getHost()); //?print_host31    System.out.println("  Port: " + e.getPort()); //?print_port
    outputConnection failed!
  12. public String getHost()

    138public String getHost() { return hostdb.example.com; } //?get_host139public int getPort() { return port; } //?get_port
  13. System.out.println(" Host: " + e.getHost()); //?print_host

    29    System.out.println("Connection failed!"); //?print_conn_error30    System.out.println("  Host: " + e.getHost()); //?print_host31    System.out.println("  Port: " + e.getPort()); //?print_port32} catch (ConfigurationException e) { //?catch_config2
    output  Host: db.example.com
  14. public int getPort()

    138public String getHost() { return host; } //?get_host139public int getPort() { return port5432; } //?get_port
  15. System.out.println(" Port: " + e.getPort()); //?print_port

    30    System.out.println("  Host: " + e.getHost()); //?print_host31    System.out.println("  Port: " + e.getPort()); //?print_port32} catch (ConfigurationException e) { //?catch_config233    System.out.println("Configuration error: " + e.getMessage()); //?print_config234}3536// When to use checked vs unchecked //?when_to_use37System.out.println("\n--- When to Use Checked ---");38System.out.println("""39    Use CHECKED custom exceptions when:40    • Caller can reasonably recover41    • External resource failure (files, network)42    • Caller should be forced to handle43    • Part of a public API contract4445    Use UNCHECKED custom exceptions when:46    • Programming error (invalid arguments)47    • Unrecoverable situation48    • Caller typically cannot handle49    • Would clutter code with try-catch50    """);5152// Demonstrate forced handling //?forced_handling53System.out.println("--- Forced Handling Demo ---");5455processConfiguration(); //?call_process_config
    output  Port: 5432
    
    --- When to Use Checked ---
    Use CHECKED custom exceptions when:
    • Caller can reasonably recover
    • External resource failure (files, network)
    • Caller should be forced to handle
    • Part of a public API contract
    
    Use UNCHECKED custom exceptions when:
    • Programming error (invalid arguments)
    • Unrecoverable situation
    • Caller typically cannot handle
    • Would clutter code with try-catch
    --- Forced Handling Demo ---
  16. static void processConfiguration()

    87static void processConfiguration() { //?process_config_method88    // Option 1: Handle with try-catch //?option189    System.out.println("Option 1: Handle with try-catch");90    try { //?try_process
    outputOption 1: Handle with try-catch
  17. static void loadConfiguration(String filename) throws ConfigurationExc…

    pass 2 of 2
    67// Method that throws checked exception //?throws_checked68static void loadConfiguration(String filenamesettings.xml) throws ConfigurationException { //?load_config_method69    // Simulate file not found //?simulate_not_found70    throw new ConfigurationException( //?throw_config71        "Configuration file not found",72        filename,73        new IOException("File does not exist: " + filename)74    );75}
  18. this.configFile ← settings.xml

    pass 2 of 2
    111public ConfigurationException(String messageConfiguration file not found, String configFilesettings.xml, Throwable causejava.io.IOException: File does not exist: settings.xml) { //?config_constructor_cause112    super(message, cause); //?super_message_cause113    this.configFile→ settings.xml = configFilesettings.xml; //?set_config_file_cause114}
  19. catch (ConfigurationException e)

    91    loadConfiguration("settings.xml"); //?load_settings92} catch (ConfigurationException eConfigurationException: Configuration file not found) { //?catch_process93    System.out.println("  Handled: " + e.getMessage()); //?handled94}
    output  Handled: Configuration file not found
  20. System.out.println(" Option 2: Propagate with throws");

    55    processConfiguration(); //?call_process_config5657    System.out.println("\n=== Key Points ===");58    System.out.println("""59        1. Extend Exception (not RuntimeException) for checked60        2. Caller MUST use try-catch or declare throws61        3. Good for recoverable external failures62        4. Forces caller to think about error handling63        5. Can still include additional data fields64        """);65}6667// Method that throws checked exception //?throws_checked68static void loadConfiguration(String filename) throws ConfigurationException { //?load_config_method69    // Simulate file not found //?simulate_not_found70    throw new ConfigurationException( //?throw_config71        "Configuration file not found",72        filename,73        new IOException("File does not exist: " + filename)74    );75}7677// Method that throws multiple checked exceptions //?throws_multiple78static void connectToService(String host, int port) //?connect_method79        throws ConnectionException, ConfigurationException { //?throws_clause80    if (host == null || host.isEmpty()) { //?check_host81        throw new ConfigurationException("Host is required", "connection.properties"); //?throw_host_error82    }83    // Simulate connection failure //?simulate_conn_fail84    throw new ConnectionException(host, port, "Connection refused"); //?throw_conn85}8687static void processConfiguration() { //?process_config_method88    // Option 1: Handle with try-catch //?option189    System.out.println("Option 1: Handle with try-catch");90    try { //?try_process91        loadConfiguration("settings.xml"); //?load_settings92    } catch (ConfigurationException e) { //?catch_process93        System.out.println("  Handled: " + e.getMessage()); //?handled94    }9596    // Option 2: Would be to declare 'throws' //?option297    System.out.println("\nOption 2: Propagate with throws");98    System.out.println("  void myMethod() throws ConfigurationException { ... }");99}
    output
    Option 2: Propagate with throws
      void myMethod() throws ConfigurationException { ... }
    
    === Key Points ===
    1. Extend Exception (not RuntimeException) for checked
    2. Caller MUST use try-catch or declare throws
    3. Good for recoverable external failures
    4. Forces caller to think about error handling
    5. Can still include additional data fields

Extend Exception for checked. Use for recoverable errors.

Best practices

Design exceptions effectively.

BestPractices.java
Replay: real traced execution (multi-file project)
// Exception Design Best Practices

public class BestPractices {
    public static void main(String[] args) {
        System.out.println("=== Exception Design Best Practices ===\n");

        // 1. Provide multiple constructors
        System.out.println("--- 1. Multiple Constructors ---");

        try {
            throw new WellDesignedException("Simple message");
        } catch (WellDesignedException e) {
            System.out.println("Basic: " + e.getMessage());
        }

        try {
            Exception cause = new Exception("Original error");
            throw new WellDesignedException("With cause", cause);
        } catch (WellDesignedException e) {
            System.out.println("With cause: " + e.getMessage());
            System.out.println("  Cause: " + e.getCause().getMessage());
        }

        // 2. Include context data
        System.out.println("\n--- 2. Include Context Data ---");

        try {
            throw new OrderException("ORD-123", "Validation failed", OrderException.Stage.VALIDATION);
        } catch (OrderException e) {
            System.out.println("Order error: " + e.getMessage());
            System.out.println("  Order ID: " + e.getOrderId());
            System.out.println("  Stage: " + e.getStage());
        }

        // 3. Use meaningful names
        System.out.println("\n--- 3. Use Meaningful Names ---");
        System.out.println("""
            Good names (clear what went wrong):
            ✓ InsufficientFundsException
            ✓ UserNotFoundException
            ✓ InvalidEmailFormatException
            ✓ OrderAlreadyShippedException

            Bad names (vague or generic):
            ✗ BadDataException
            ✗ MyException
            ✗ Error1
            ✗ ProcessingException
            """);

        // 4. Document your exceptions
        System.out.println("--- 4. Document with @throws ---");
        System.out.println("""
            /**
             * Processes the order.
             * @param orderId the order to process
             * @throws OrderNotFoundException if order doesn't exist
             * @throws OrderAlreadyProcessedException if already processed
             */
            void processOrder(String orderId) { ... }
            """);

        // 5. Preserve the cause chain
        System.out.println("--- 5. Preserve Cause Chain ---");

        try {
            simulateDatabaseError();
        } catch (DataAccessException e) {
            System.out.println("Exception chain:");
            Throwable current = e;
            int level = 0;
            while (current != null) {
                System.out.println("  " + "  ".repeat(level) +
                    current.getClass().getSimpleName() + ": " + current.getMessage());
                current = current.getCause();
                level++;
            }
        }

        // 6. Consider immutability
        System.out.println("\n--- 6. Make Fields Final ---");
        System.out.println("""
            // Good: final fields
            class MyException extends RuntimeException {
                private final String code;  // Cannot change
                private final int value;    // Cannot change
            }

            // Avoid: mutable state in exceptions
            """);

        System.out.println("\n=== Summary ===");
        System.out.println("""
            ✓ Provide multiple constructors
            ✓ Include relevant context data
            ✓ Use clear, descriptive names
            ✓ Document in Javadoc with @throws
            ✓ Always preserve cause chain
            ✓ Make exception fields final
            ✓ Follow naming convention (*Exception)
            """);
    }

    static void simulateDatabaseError() {
        try {
            throw new RuntimeException("Connection timeout");
        } catch (RuntimeException e) {
            throw new DataAccessException("Failed to execute query", e);
        }
    }
}

// Well-designed exception with multiple constructors
class WellDesignedException extends RuntimeException {

    // Constructor 1: Message only
    public WellDesignedException(String message) {
        super(message);
    }

    // Constructor 2: Message + cause
    public WellDesignedException(String message, Throwable cause) {
        super(message, cause);
    }

    // Constructor 3: Cause only
    public WellDesignedException(Throwable cause) {
        super(cause);
    }
}

// Exception with context data
class OrderException extends RuntimeException {

    // Enum for processing stage
    public enum Stage {
        VALIDATION,
        PAYMENT,
        SHIPPING,
        COMPLETION
    }

    private final String orderId;
    private final Stage stage;

    public OrderException(String orderId, String message, Stage stage) {
        super(String.format("[%s] %s (stage: %s)", orderId, message, stage));
        this.orderId = orderId;
        this.stage = stage;
    }

    public OrderException(String orderId, String message, Stage stage, Throwable cause) {
        super(String.format("[%s] %s (stage: %s)", orderId, message, stage), cause);
        this.orderId = orderId;
        this.stage = stage;
    }

    public String getOrderId() { return orderId; }
    public Stage getStage() { return stage; }
}

// Exception that preserves cause chain
class DataAccessException extends RuntimeException {

    public DataAccessException(String message) {
        super(message);
    }

    public DataAccessException(String message, Throwable cause) {
        super(message, cause);
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
  1. public static void main(String[] args)

    3public class BestPractices {4    public static void main(String[] args) {5        System.out.println("=== Exception Design Best Practices ===\n");67        // 1. Provide multiple constructors //?multiple_constructors8        System.out.println("--- 1. Multiple Constructors ---");
    output=== Exception Design Best Practices ===
    --- 1. Multiple Constructors ---
  2. public WellDesignedException(String message)

    116// Constructor 1: Message only //?constructor1117public WellDesignedException(String messageSimple message) { //?constructor_message118    super(message); //?super1
  3. catch (WellDesignedException e)

    11    throw new WellDesignedException("Simple message"); //?throw_basic12} catch (WellDesignedException eWellDesignedException: Simple message) { //?catch_basic13    System.out.println("Basic: " + e.getMessage()); //?print_basic14}
    outputBasic: Simple message
  4. cause ← java.lang.Exception: Original error

    16try { //?try_with_cause17    Exception cause→ java.lang.Exception: Original error = new Exception("Original error"); //?create_cause18    throw new WellDesignedException("With cause", cause); //?throw_with_cause19} catch (WellDesignedException e) { //?catch_with_cause
  5. public WellDesignedException(String message, Throwable cause)

    121// Constructor 2: Message + cause //?constructor2122public WellDesignedException(String messageWith cause, Throwable causejava.lang.Exception: Original error) { //?constructor_cause123    super(message, cause); //?super2
  6. catch (WellDesignedException e)

    18    throw new WellDesignedException("With cause", cause); //?throw_with_cause19} catch (WellDesignedException eWellDesignedException: With cause) { //?catch_with_cause20    System.out.println("With cause: " + e.getMessage()); //?print_with_cause21    System.out.println("  Cause: " + e.getCause().getMessage()); //?print_cause_msg22}
    outputWith cause: With cause
      Cause: Original error
  7. System.out.println(" --- 2. Include Context Data ---");

    24// 2. Include context data //?context_data25System.out.println("\n--- 2. Include Context Data ---");
    output
    --- 2. Include Context Data ---
  8. this.orderId ← ORD-123, this.stage ← VALIDATION

    146public OrderException(String orderIdORD-123, String messageValidation failed, Stage stageVALIDATION) { //?order_constructor147    super(String.format("[%s] %s (stage: %s)", orderId, message, stage)); //?order_super148    this.orderId→ ORD-123 = orderIdORD-123; //?set_order_id149    this.stage→ VALIDATION = stageVALIDATION; //?set_stage150}
  9. catch (OrderException e)

    28    throw new OrderException("ORD-123", "Validation failed", OrderException.Stage.VALIDATION); //?throw_context29} catch (OrderException eOrderException: [ORD-123] Validation failed (stage: VALIDATION)) { //?catch_context30    System.out.println("Order error: " + e.getMessage()); //?print_context31    System.out.println("  Order ID: " + e.getOrderId()); //?print_order_id32    System.out.println("  Stage: " + e.getStage()); //?print_stage
    outputOrder error: [ORD-123] Validation failed (stage: VALIDATION)
  10. public String getOrderId()

    158public String getOrderId() { return orderIdORD-123; } //?get_order_id159public Stage getStage() { return stage; } //?get_stage
  11. System.out.println(" Order ID: " + e.getOrderId()); //?print_order_id

    30    System.out.println("Order error: " + e.getMessage()); //?print_context31    System.out.println("  Order ID: " + e.getOrderId()); //?print_order_id32    System.out.println("  Stage: " + e.getStage()); //?print_stage33}
    output  Order ID: ORD-123
  12. public Stage getStage()

    158    public String getOrderId() { return orderId; } //?get_order_id159    public Stage getStage() { return stageVALIDATION; } //?get_stage160}
  13. System.out.println(" Stage: " + e.getStage()); //?print_stage

    31    System.out.println("  Order ID: " + e.getOrderId()); //?print_order_id32    System.out.println("  Stage: " + e.getStage()); //?print_stage33}3435// 3. Use meaningful names //?meaningful_names36System.out.println("\n--- 3. Use Meaningful Names ---");37System.out.println("""38    Good names (clear what went wrong):39    ✓ InsufficientFundsException40    ✓ UserNotFoundException41    ✓ InvalidEmailFormatException42    ✓ OrderAlreadyShippedException4344    Bad names (vague or generic):45    ✗ BadDataException46    ✗ MyException47    ✗ Error148    ✗ ProcessingException49    """);5051// 4. Document your exceptions //?document52System.out.println("--- 4. Document with @throws ---");53System.out.println("""54    /**55     * Processes the order.56     * @param orderId the order to process57     * @throws OrderNotFoundException if order doesn't exist58     * @throws OrderAlreadyProcessedException if already processed59     */60    void processOrder(String orderId) { ... }61    """);6263// 5. Preserve the cause chain //?preserve_cause64System.out.println("--- 5. Preserve Cause Chain ---");
    output  Stage: VALIDATION
    
    --- 3. Use Meaningful Names ---
    Good names (clear what went wrong):
    ✓ InsufficientFundsException
    ✓ UserNotFoundException
    ✓ InvalidEmailFormatException
    ✓ OrderAlreadyShippedException
    
    Bad names (vague or generic):
    ✗ BadDataException
    ✗ MyException
    ✗ Error1
    ✗ ProcessingException
    --- 4. Document with @throws ---
    /**
     * Processes the order.
     * @param orderId the order to process
     * @throws OrderNotFoundException if order doesn't exist
     * @throws OrderAlreadyProcessedException if already processed
     */
    void processOrder(String orderId) { ... }
    --- 5. Preserve Cause Chain ---
  14. catch (RuntimeException e)

    106    throw new RuntimeException("Connection timeout"); //?throw_timeout107} catch (RuntimeException ejava.lang.RuntimeException: Connection timeout) { //?catch_simulate108    throw new DataAccessException("Failed to execute query", e); //?throw_data_access109}
  15. public DataAccessException(String message, Throwable cause)

    169public DataAccessException(String messageFailed to execute query, Throwable causejava.lang.RuntimeException: Connection timeout) { //?da_constructor_cause170    super(message, cause); //?da_super_cause
  16. current ← DataAccessException: Failed to execute query, level ← 0

    67    simulateDatabaseError(); //?call_simulate68} catch (DataAccessException eDataAccessException: Failed to execute query) { //?catch_chain69    System.out.println("Exception chain:"); //?print_chain_header70    Throwable current→ DataAccessException: Failed to execute query = e; //?current_init71    int level→ 0 = 0; //?level_init72    while (current != null) { //?while_chain
    outputException chain:
  17. current ← java.lang.RuntimeException: Connection timeout, level ← 1

    pass 1 of 2
    71int level = 0; //?level_init72while (currentDataAccessException: Failed to execute query != null) { //?while_chain73    System.out.println("  " + "  ".repeat(level0) + //?print_chain74        current.getClass().getSimpleName() + ": " + current.getMessage());75    current→ java.lang.RuntimeException: Connection timeout = current.getCause(); //?next_cause76    level→ 1++; //?increment_level77}
    output  DataAccessException: Failed to execute query
  18. current ← null, level ← 2

    pass 2 of 2
    71int level = 0; //?level_init72while (currentjava.lang.RuntimeException: Connection timeout != null) { //?while_chain73    System.out.println("  " + "  ".repeat(level1) + //?print_chain74        current.getClass().getSimpleName() + ": " + current.getMessage());75    current→ null = current.getCause(); //?next_cause76    level→ 2++; //?increment_level77}
    output    RuntimeException: Connection timeout
  19. System.out.println(" --- 6. Make Fields Final ---");

    80    // 6. Consider immutability //?immutability81    System.out.println("\n--- 6. Make Fields Final ---");82    System.out.println("""83        // Good: final fields84        class MyException extends RuntimeException {85            private final String code;  // Cannot change86            private final int value;    // Cannot change87        }8889        // Avoid: mutable state in exceptions90        """);9192    System.out.println("\n=== Summary ===");93    System.out.println("""94        ✓ Provide multiple constructors95        ✓ Include relevant context data96        ✓ Use clear, descriptive names97        ✓ Document in Javadoc with @throws98        ✓ Always preserve cause chain99        ✓ Make exception fields final100        ✓ Follow naming convention (*Exception)101        """);102}
    output
    --- 6. Make Fields Final ---
    // Good: final fields
    class MyException extends RuntimeException {
        private final String code;  // Cannot change
        private final int value;    // Cannot change
    }
    
    // Avoid: mutable state in exceptions
    
    === Summary ===
    ✓ Provide multiple constructors
    ✓ Include relevant context data
    ✓ Use clear, descriptive names
    ✓ Document in Javadoc with @throws
    ✓ Always preserve cause chain
    ✓ Make exception fields final
    ✓ Follow naming convention (*Exception)

Include cause, make immutable, provide useful messages.

Exercise: Practical.java

Build a banking system with domain-specific exceptions