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
throwandthrowskeywords - Implement exception chaining and wrapping
- Practice advanced exception handling strategies
- Build robust error handling systems
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
InsufficientFundsExceptionInvalidEmailExceptionUserNotFoundExceptionProductOutOfStockExceptionInvalidPasswordException
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);
}
}
}
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 |
|---|---|---|
| Purpose | Explicitly throw an exception | Declare exceptions that method might throw |
| Location | Inside method body | In method signature |
| Syntax | throw new ExceptionType(); | methodName() throws ExceptionType |
| Exception Object | Followed by exception instance | Followed by exception class name |
| Multiple Exceptions | One exception at a time | Can 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());
}
}
}
}
throw: Actively throws exceptions inside method bodythrows: Declares that method might throw certain exceptions- Checked exceptions require
throwsdeclaration - Unchecked exceptions don't require
throwsdeclaration
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());
}
}
}
}
- 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, andOrderProcessorclasses - Handle edge cases: negative quantities, unavailable products, insufficient stock
- Implement exception chaining for order processing failures
- Create comprehensive test scenarios demonstrating all exception types
📚 Lecture Summary
Key Concepts Covered
- Custom exception design principles
- Checked vs unchecked custom exceptions
throwvsthrowskeywords- 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

