Skip to main content
  1. Resources/
  2. Study Materials/
  3. Information & Communication Technology Engineering/
  4. ICT Semester 4/
  5. Java Programming (4343203)/

14 mins· ·
Milav Dabgar
Author
Milav Dabgar
Experienced lecturer in the electrical and electronic manufacturing industry. Skilled in Embedded Systems, Image Processing, Data Science, MATLAB, Python, STM32. Strong education professional with a Master’s degree in Communication Systems Engineering from L.D. College of Engineering - Ahmedabad.
Lecture 14: Custom Exceptions & Advanced Handling | Java Programming (4343203)

Custom Exceptions & Advanced Handling

Java Programming (4343203)

Lecture 14

Unit 3: Exception Handling - Advanced Topics
GTU Computer Engineering Semester 4

Learning Objectives

  • Understand the need for custom exceptions
  • Learn to create user-defined exception classes
  • Master the throw and throws keywords
  • Implement exception chaining and wrapping
  • Practice advanced exception handling strategies
  • Build robust error handling systems
Focus: Creating maintainable and informative error handling systems using custom exceptions.

Why Create Custom Exceptions?

Business Logic Errors

  • Domain-specific errors
  • Validation failures
  • Business rule violations
  • Application-specific conditions

Better Error Communication

  • Meaningful error messages
  • Clear error identification
  • Specific handling strategies

Examples of Custom Exceptions

  • InsufficientFundsException
  • InvalidEmailException
  • UserNotFoundException
  • ProductOutOfStockException
  • InvalidPasswordException
Note: Custom exceptions should extend appropriate base classes (Exception, RuntimeException, etc.)

Creating Custom Exception Classes

1. Checked Custom Exception


// Custom checked exception
public class InsufficientFundsException extends Exception {
    private double balance;
    private double requestedAmount;
    
    // Default constructor
    public InsufficientFundsException() {
        super("Insufficient funds");
    }
    
    // Constructor with message
    public InsufficientFundsException(String message) {
        super(message);
    }
    
    // Constructor with detailed information
    public InsufficientFundsException(double balance, double requestedAmount) {
        super("Insufficient funds: Balance=" + balance + 
              ", Requested=" + requestedAmount);
        this.balance = balance;
        this.requestedAmount = requestedAmount;
    }
    
    // Constructor with message and cause
    public InsufficientFundsException(String message, Throwable cause) {
        super(message, cause);
    }
    
    // Getters
    public double getBalance() { return balance; }
    public double getRequestedAmount() { return requestedAmount; }
    public double getShortfall() { return requestedAmount - balance; }
}
                

Runtime Custom Exceptions

2. Unchecked Custom Exception


// Custom runtime exception
public class InvalidEmailException extends RuntimeException {
    private String invalidEmail;
    private String reason;
    
    public InvalidEmailException(String email, String reason) {
        super("Invalid email '" + email + "': " + reason);
        this.invalidEmail = email;
        this.reason = reason;
    }
    
    public String getInvalidEmail() { return invalidEmail; }
    public String getReason() { return reason; }
}

// Usage example
public class EmailValidator {
    public static void validateEmail(String email) {
        if (email == null || email.trim().isEmpty()) {
            throw new InvalidEmailException(email, "Email cannot be null or empty");
        }
        
        if (!email.contains("@")) {
            throw new InvalidEmailException(email, "Email must contain '@' symbol");
        }
        
        if (!email.contains(".")) {
            throw new InvalidEmailException(email, "Email must contain domain extension");
        }
    }
}
                

The 'throw' and 'throws' Keywords

'throw' Keyword

  • Used to explicitly throw an exception
  • Used inside method body
  • Followed by exception object
  • Can throw only one exception at a time

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

'throws' Keyword

  • Used in method declaration
  • Declares exceptions that method might throw
  • Followed by exception class names
  • Can declare multiple exceptions

public void processPayment(double amount) 
    throws InsufficientFundsException, 
           NetworkException {
    // Method implementation
    validateBalance(amount);
    connectToBank();
}
                        

Complete Banking System Example


public class BankAccount {
    private String accountNumber;
    private double balance;
    private boolean isActive;
    
    public BankAccount(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
        this.isActive = true;
    }
    
    public void withdraw(double amount) throws InsufficientFundsException,
                                              AccountClosedException {
        // Validate account status
        if (!isActive) {
            throw new AccountClosedException("Account " + accountNumber + 
                                           " has been closed");
        }
        
        // Validate amount
        if (amount <= 0) {
            throw new IllegalArgumentException("Withdrawal amount must be positive");
        }
        
        // Check sufficient funds
        if (amount > balance) {
            throw new InsufficientFundsException(balance, amount);
        }
        
        balance -= amount;
        System.out.println("Withdrawn: $" + amount + ", Balance: $" + balance);
    }
    
    public void deposit(double amount) throws AccountClosedException {
        if (!isActive) {
            throw new AccountClosedException("Cannot deposit to closed account");
        }
        
        if (amount <= 0) {
            throw new IllegalArgumentException("Deposit amount must be positive");
        }
        
        balance += amount;
        System.out.println("Deposited: $" + amount + ", Balance: $" + balance);
    }
    
    public double getBalance() { return balance; }
    public void closeAccount() { this.isActive = false; }
}
                

Additional Custom Exception


public class AccountClosedException extends Exception {
    private String accountNumber;
    private Date closureDate;
    
    public AccountClosedException(String message) {
        super(message);
    }
    
    public AccountClosedException(String accountNumber, Date closureDate) {
        super("Account " + accountNumber + " was closed on " + closureDate);
        this.accountNumber = accountNumber;
        this.closureDate = closureDate;
    }
    
    public String getAccountNumber() { return accountNumber; }
    public Date getClosureDate() { return closureDate; }
}

// Usage in banking application
public class BankingApplication {
    public static void main(String[] args) {
        BankAccount account = new BankAccount("12345", 1000.0);
        
        try {
            account.withdraw(500.0);  // Success
            account.withdraw(700.0);  // Insufficient funds
        } catch (InsufficientFundsException e) {
            System.err.println("Transaction failed: " + e.getMessage());
            System.err.println("Shortfall: $" + e.getShortfall());
        } catch (AccountClosedException e) {
            System.err.println("Account error: " + e.getMessage());
        }
    }
}
                

Exception Chaining and Wrapping

Preserving Original Exception Information


public class DatabaseException extends Exception {
    public DatabaseException(String message) {
        super(message);
    }
    
    public DatabaseException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class UserService {
    private DatabaseConnection db;
    
    public User findUserById(int userId) throws DatabaseException {
        try {
            // Database operation that might throw SQLException
            return db.queryUser("SELECT * FROM users WHERE id = ?", userId);
        } catch (SQLException sqlEx) {
            // Chain the original exception
            throw new DatabaseException(
                "Failed to find user with ID: " + userId, sqlEx);
        } catch (ConnectionException connEx) {
            // Chain connection exception
            throw new DatabaseException(
                "Database connection failed while finding user: " + userId, 
                connEx);
        }
    }
}

// Using exception chaining
public class Application {
    public static void main(String[] args) {
        UserService service = new UserService();
        
        try {
            User user = service.findUserById(123);
        } catch (DatabaseException e) {
            System.err.println("Main error: " + e.getMessage());
            
            // Access the original cause
            Throwable cause = e.getCause();
            if (cause != null) {
                System.err.println("Root cause: " + cause.getMessage());
                cause.printStackTrace();
            }
        }
    }
}
                

Exception Design Best Practices

1. Exception Hierarchy


// Base application exception
public class ApplicationException 
    extends Exception {
    protected ApplicationException(String msg) {
        super(msg);
    }
}

// Specific business exceptions
public class ValidationException 
    extends ApplicationException {
    public ValidationException(String msg) {
        super("Validation failed: " + msg);
    }
}

public class BusinessLogicException 
    extends ApplicationException {
    public BusinessLogicException(String msg) {
        super("Business rule violation: " + msg);
    }
}
                        

2. Exception Information


public class PaymentException extends Exception {
    private String transactionId;
    private PaymentMethod method;
    private double amount;
    private ErrorCode errorCode;
    
    public PaymentException(String transactionId, 
                           PaymentMethod method,
                           double amount, 
                           ErrorCode code, 
                           String message) {
        super(message);
        this.transactionId = transactionId;
        this.method = method;
        this.amount = amount;
        this.errorCode = code;
    }
    
    // Getters for debugging
    public String getTransactionId() { 
        return transactionId; 
    }
    public PaymentMethod getMethod() { 
        return method; 
    }
    public double getAmount() { 
        return amount; 
    }
    public ErrorCode getErrorCode() { 
        return errorCode; 
    }
}
                        

Advanced Exception Handling Patterns

1. Exception Translation Pattern


public class OrderService {
    private PaymentService paymentService;
    private InventoryService inventoryService;
    
    public void processOrder(Order order) throws OrderProcessingException {
        try {
            // Check inventory
            inventoryService.reserveItems(order.getItems());
            
            // Process payment
            paymentService.processPayment(order.getPayment());
            
            // Confirm order
            confirmOrder(order);
            
        } catch (InsufficientStockException e) {
            throw new OrderProcessingException(
                "Order cannot be fulfilled due to insufficient stock", e);
        } catch (PaymentException e) {
            // Release reserved inventory
            inventoryService.releaseReservation(order.getItems());
            throw new OrderProcessingException(
                "Order failed due to payment error", e);
        } catch (Exception e) {
            // Handle unexpected errors
            rollbackOrder(order);
            throw new OrderProcessingException(
                "Unexpected error during order processing", e);
        }
    }
    
    private void rollbackOrder(Order order) {
        // Cleanup operations
        try {
            inventoryService.releaseReservation(order.getItems());
            paymentService.refundPayment(order.getPayment());
        } catch (Exception rollbackEx) {
            // Log rollback failures
            System.err.println("Rollback failed: " + rollbackEx.getMessage());
        }
    }
}
                

GTU Previous Year Question (Summer 2022)

Q: Explain the concept of custom exceptions in Java. Create a custom exception class for handling invalid age scenarios (age should be between 0 and 150). Write a complete program demonstrating the use of this exception.

Solution:


// Custom exception for invalid age
class InvalidAgeException extends Exception {
    private int invalidAge;
    private String reason;
    
    public InvalidAgeException(int age, String reason) {
        super("Invalid age: " + age + " - " + reason);
        this.invalidAge = age;
        this.reason = reason;
    }
    
    public int getInvalidAge() { return invalidAge; }
    public String getReason() { return reason; }
}

// Person class using custom exception
class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) throws InvalidAgeException {
        setName(name);
        setAge(age);
    }
    
    public void setAge(int age) throws InvalidAgeException {
        if (age < 0) {
            throw new InvalidAgeException(age, "Age cannot be negative");
        }
        if (age > 150) {
            throw new InvalidAgeException(age, "Age cannot exceed 150 years");
        }
        this.age = age;
    }
    
    public void setName(String name) {
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("Name cannot be null or empty");
        }
        this.name = name;
    }
    
    public String getName() { return name; }
    public int getAge() { return age; }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}
                

Complete Demonstration Program:


public class CustomExceptionDemo {
    public static void main(String[] args) {
        // Test cases for custom exception
        int[] testAges = {25, -5, 200, 0, 150, 151};
        String[] names = {"John", "Alice", "Bob", "Charlie", "Diana", "Eve"};
        
        for (int i = 0; i < testAges.length; i++) {
            try {
                Person person = new Person(names[i], testAges[i]);
                System.out.println("✓ Successfully created: " + person);
                
                // Test age update
                if (i == 0) {  // Test updating John's age
                    person.setAge(30);
                    System.out.println("✓ Successfully updated age: " + person);
                }
                
            } catch (InvalidAgeException e) {
                System.err.println("✗ InvalidAgeException: " + e.getMessage());
                System.err.println("  Invalid age: " + e.getInvalidAge());
                System.err.println("  Reason: " + e.getReason());
                
            } catch (IllegalArgumentException e) {
                System.err.println("✗ IllegalArgumentException: " + e.getMessage());
            }
            System.out.println(); // Empty line for readability
        }
        
        // Demonstrate exception chaining
        System.out.println("=== Exception Chaining Demo ===");
        try {
            createPersonFromString("InvalidPerson,-10");
        } catch (Exception e) {
            System.err.println("Caught exception: " + e.getMessage());
            if (e.getCause() != null) {
                System.err.println("Root cause: " + e.getCause().getMessage());
            }
        }
    }
    
    // Method demonstrating exception chaining
    public static Person createPersonFromString(String personData) 
            throws Exception {
        try {
            String[] parts = personData.split(",");
            String name = parts[0];
            int age = Integer.parseInt(parts[1]);
            return new Person(name, age);
        } catch (InvalidAgeException e) {
            throw new Exception("Failed to create person from string: " + 
                              personData, e);
        } catch (NumberFormatException e) {
            throw new Exception("Invalid age format in string: " + 
                              personData, e);
        }
    }
}
                
Key Points Covered: Custom exception creation, meaningful error messages, exception chaining, and comprehensive error handling.

GTU Previous Year Question (Winter 2022)

Q: Explain the difference between 'throw' and 'throws' keywords in Java. Write a program to demonstrate both keywords with appropriate examples.

Comparison Table:

Aspect'throw' Keyword'throws' Keyword
PurposeExplicitly throw an exceptionDeclare exceptions that method might throw
LocationInside method bodyIn method signature
Syntaxthrow new ExceptionType();methodName() throws ExceptionType
Exception ObjectFollowed by exception instanceFollowed by exception class name
Multiple ExceptionsOne exception at a timeCan declare multiple exceptions

Demonstration Program:


// Custom exceptions for demonstration
class NegativeNumberException extends Exception {
    public NegativeNumberException(String message) {
        super(message);
    }
}

class ZeroDivisionException extends Exception {
    public ZeroDivisionException(String message) {
        super(message);
    }
}

public class ThrowVsThrowsDemo {
    
    // Method using 'throws' keyword - declares exceptions
    public static double calculateSquareRoot(double number) 
            throws NegativeNumberException {
        
        // Using 'throw' keyword - explicitly throwing exception
        if (number < 0) {
            throw new NegativeNumberException(
                "Cannot calculate square root of negative number: " + number);
        }
        
        return Math.sqrt(number);
    }
    
    // Method using 'throws' with multiple exceptions
    public static double divide(double dividend, double divisor) 
            throws ZeroDivisionException, NegativeNumberException {
        
        // Using 'throw' for zero division
        if (divisor == 0) {
            throw new ZeroDivisionException(
                "Division by zero is not allowed");
        }
        
        // Using 'throw' for negative dividend
        if (dividend < 0) {
            throw new NegativeNumberException(
                "Dividend cannot be negative: " + dividend);
        }
        
        return dividend / divisor;
    }
    
    // Method demonstrating unchecked exception with 'throw'
    public static void validateAge(int age) {
        // 'throw' with unchecked exception (no 'throws' needed)
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException(
                "Invalid age: " + age + ". Age must be between 0 and 150.");
        }
    }
}
                

Main Method Demonstration:


public class MainDemo {
    public static void main(String[] args) {
        System.out.println("=== Demonstrating 'throw' and 'throws' ===\n");
        
        // Test 1: Square root calculation
        double[] numbers = {25.0, -4.0, 0.0, 16.0};
        
        for (double num : numbers) {
            try {
                double result = ThrowVsThrowsDemo.calculateSquareRoot(num);
                System.out.println("√" + num + " = " + result);
            } catch (NegativeNumberException e) {
                System.err.println("Error: " + e.getMessage());
            }
        }
        
        System.out.println("\n" + "=".repeat(40) + "\n");
        
        // Test 2: Division operation
        double[][] divisionTests = {
            {10.0, 2.0}, {15.0, 0.0}, {-8.0, 4.0}, {20.0, 5.0}
        };
        
        for (double[] test : divisionTests) {
            try {
                double result = ThrowVsThrowsDemo.divide(test[0], test[1]);
                System.out.println(test[0] + " ÷ " + test[1] + " = " + result);
            } catch (ZeroDivisionException | NegativeNumberException e) {
                System.err.println("Division Error: " + e.getMessage());
            }
        }
        
        System.out.println("\n" + "=".repeat(40) + "\n");
        
        // Test 3: Age validation (unchecked exception)
        int[] ages = {25, -5, 200, 30};
        
        for (int age : ages) {
            try {
                ThrowVsThrowsDemo.validateAge(age);
                System.out.println("Valid age: " + age);
            } catch (IllegalArgumentException e) {
                System.err.println("Validation Error: " + e.getMessage());
            }
        }
    }
}
                
Key Differences Demonstrated:
  • throw: Actively throws exceptions inside method body
  • throws: Declares that method might throw certain exceptions
  • Checked exceptions require throws declaration
  • Unchecked exceptions don't require throws declaration

GTU Previous Year Question (Summer 2023)

Q: Write a Java program to create a custom exception for bank account operations. The program should handle cases like insufficient funds, invalid account numbers, and closed accounts. Demonstrate proper exception handling with try-catch blocks.

Complete Banking System Solution:


// Custom exceptions for banking operations
class InsufficientFundsException extends Exception {
    private double balance, 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; }
}

class InvalidAccountException extends Exception {
    private String accountNumber;
    
    public InvalidAccountException(String accountNumber, String reason) {
        super("Invalid account '" + accountNumber + "': " + reason);
        this.accountNumber = accountNumber;
    }
    
    public String getAccountNumber() { return accountNumber; }
}

class ClosedAccountException extends Exception {
    private String accountNumber;
    private java.util.Date closureDate;
    
    public ClosedAccountException(String accountNumber) {
        super("Account '" + accountNumber + "' is closed");
        this.accountNumber = accountNumber;
        this.closureDate = new java.util.Date(); // Simulate closure date
    }
    
    public String getAccountNumber() { return accountNumber; }
    public java.util.Date getClosureDate() { return closureDate; }
}

// Bank account class
class BankAccount {
    private String accountNumber;
    private String accountHolder;
    private double balance;
    private boolean isActive;
    
    public BankAccount(String accountNumber, String holder, double initialBalance) 
            throws InvalidAccountException {
        validateAccountNumber(accountNumber);
        this.accountNumber = accountNumber;
        this.accountHolder = holder;
        this.balance = initialBalance;
        this.isActive = true;
    }
    
    private void validateAccountNumber(String accountNumber) 
            throws InvalidAccountException {
        if (accountNumber == null || accountNumber.trim().isEmpty()) {
            throw new InvalidAccountException(accountNumber, "Account number cannot be empty");
        }
        if (accountNumber.length() != 10) {
            throw new InvalidAccountException(accountNumber, "Account number must be 10 digits");
        }
        if (!accountNumber.matches("\\d+")) {
            throw new InvalidAccountException(accountNumber, "Account number must contain only digits");
        }
    }
}
                

Banking Operations Implementation:


    // Banking operations with exception handling
    public void deposit(double amount) throws ClosedAccountException {
        if (!isActive) {
            throw new ClosedAccountException(accountNumber);
        }
        if (amount <= 0) {
            throw new IllegalArgumentException("Deposit amount must be positive");
        }
        
        balance += amount;
        System.out.printf("Deposited: $%.2f, New Balance: $%.2f%n", amount, balance);
    }
    
    public void withdraw(double amount) throws InsufficientFundsException, 
                                              ClosedAccountException {
        if (!isActive) {
            throw new ClosedAccountException(accountNumber);
        }
        if (amount <= 0) {
            throw new IllegalArgumentException("Withdrawal amount must be positive");
        }
        if (amount > balance) {
            throw new InsufficientFundsException(balance, amount);
        }
        
        balance -= amount;
        System.out.printf("Withdrawn: $%.2f, New Balance: $%.2f%n", amount, balance);
    }
    
    public void closeAccount() {
        this.isActive = false;
        System.out.println("Account " + accountNumber + " has been closed.");
    }
    
    public void displayAccountInfo() {
        System.out.printf("Account: %s, Holder: %s, Balance: $%.2f, Status: %s%n",
                         accountNumber, accountHolder, balance, 
                         isActive ? "Active" : "Closed");
    }
    
    // Getters
    public String getAccountNumber() { return accountNumber; }
    public double getBalance() { return balance; }
    public boolean isActive() { return isActive; }
}
                

Complete Banking Demo:


public class BankingSystemDemo {
    public static void main(String[] args) {
        System.out.println("=== Banking System with Custom Exceptions ===\n");
        
        try {
            // Test 1: Create valid account
            BankAccount account1 = new BankAccount("1234567890", "John Doe", 1000.0);
            account1.displayAccountInfo();
            
            // Test 2: Valid operations
            account1.deposit(500.0);
            account1.withdraw(300.0);
            
            System.out.println();
            
            // Test 3: Invalid operations
            System.out.println("--- Testing Exception Scenarios ---");
            
            // Insufficient funds
            try {
                account1.withdraw(2000.0);
            } catch (InsufficientFundsException e) {
                System.err.println("❌ " + e.getMessage());
                System.err.printf("   Shortfall: $%.2f%n", e.getShortfall());
            }
            
            // Test 4: Closed account operations
            account1.closeAccount();
            try {
                account1.deposit(100.0);
            } catch (ClosedAccountException e) {
                System.err.println("❌ " + e.getMessage());
            }
            
            try {
                account1.withdraw(50.0);
            } catch (ClosedAccountException e) {
                System.err.println("❌ " + e.getMessage());
            }
            
        } catch (InvalidAccountException e) {
            System.err.println("❌ Account Creation Failed: " + e.getMessage());
        } catch (Exception e) {
            System.err.println("❌ Unexpected error: " + e.getMessage());
        }
        
        System.out.println("\n--- Testing Invalid Account Numbers ---");
        
        // Test 5: Invalid account numbers
        String[] invalidAccountNumbers = {"", "123", "12345abcde", "12345678901", null};
        
        for (String accNum : invalidAccountNumbers) {
            try {
                BankAccount invalidAccount = new BankAccount(accNum, "Test User", 1000.0);
                System.out.println("✓ Account created: " + accNum);
            } catch (InvalidAccountException e) {
                System.err.println("❌ " + e.getMessage());
            } catch (Exception e) {
                System.err.println("❌ Unexpected error for account " + accNum + ": " + e.getMessage());
            }
        }
    }
}
                
Complete Solution Features:
  • Three custom exceptions for different banking scenarios
  • Comprehensive validation and error handling
  • Detailed exception information with getter methods
  • Real-world banking operations simulation
  • Proper exception propagation and handling

🧪 Hands-on Lab Exercise

Lab 14: E-commerce Exception System

Task: Create a complete e-commerce system with custom exceptions for product management, shopping cart operations, and order processing.

Requirements:

  • Create custom exceptions: ProductNotFoundException, OutOfStockException, InvalidQuantityException
  • Implement Product, ShoppingCart, and OrderProcessor classes
  • Handle edge cases: negative quantities, unavailable products, insufficient stock
  • Implement exception chaining for order processing failures
  • Create comprehensive test scenarios demonstrating all exception types
Challenge: Implement a rollback mechanism when order processing fails at any stage.

📚 Lecture Summary

Key Concepts Covered

  • Custom exception design principles
  • Checked vs unchecked custom exceptions
  • throw vs throws keywords
  • Exception chaining and wrapping
  • Exception hierarchy design
  • Advanced error handling patterns

Best Practices

  • Create meaningful exception names
  • Include relevant error information
  • Use exception chaining appropriately
  • Design exception hierarchies
  • Handle exceptions at appropriate levels
  • Provide recovery mechanisms where possible

🎯 Next Lecture Preview

Lecture 15: Multi-threading Fundamentals

  • Thread creation and lifecycle
  • Thread synchronization basics
  • Race conditions and thread safety
  • Producer-consumer problem