Home
6Exception Handling & Custom Exceptions
6

Lab Manual VI · CSE 202

Exception Handling & Custom Exceptions

Make a bank account that refuses bad input: throw and declare exceptions, catch them with try-catch-finally, write checked and unchecked custom exceptions and wrap low-level failures in a meaningful one.

← → move between slides · F full screen · M slide list

Before we start

Why this lab

A customer tries to withdraw 10,000 from an account that holds 5,000. Should the program crash, quietly do nothing, or explain what went wrong?

It should throw an exception that the caller can catch and report. In this lab the bank speaks in exceptions: InvalidAccountException, InsufficientFundsException and TransactionException.

Lab 6 of 10

Lab at a glance

After this lab you can

  • Explain why programs handle exceptions
  • Describe the Throwable hierarchy and checked vs unchecked exceptions
  • Handle errors with try, catch and finally
  • Throw exceptions with throw and declare them with throws
  • Write custom checked and unchecked exceptions
  • Wrap a low-level exception in a higher-level one, keeping the cause

Before you start

  • Labs 2–4: classes, constructors, inheritance
  • Conditionals and validation logic

Plan for the session

  1. Theory recap
  2. Walk through the program, section by section, and run it
  3. In-lab tasks, shown to the instructor
  4. Report exercises at home

Theory 1 of 5

What is an exception?

  • An exception is an object thrown when something abnormal happens; it interrupts the normal flow.
  • A matching catch handles it; if nobody catches it, the program stops with a stack trace.

What is an exception?

try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("Error: " + e.getMessage());
}

Theory 2 of 5

The exception hierarchy

Theory 2 of 5

The exception hierarchy (cont.)

  • Error: serious JVM problems — do not catch.
  • Exception: conditions a program can handle.
  • RuntimeException and its subclasses are unchecked.

Theory 3 of 5

Checked vs unchecked

CheckedUnchecked
ExtendsExceptionRuntimeException
Compilermust catch or declarenot enforced
Typical causeoutside problem (file, bad account data)programming error / business rule
In this labInvalidAccountException, TransactionExceptionInsufficientFundsException

Theory 4 of 5

throw, throws and finally

public void deposit(double amount) throws InvalidAccountException {
    if (amount <= 0) {
        throw new InvalidAccountException("Deposit amount must be positive.");
    }
    balance += amount;
}

try { account.deposit(500.0); }
catch (InvalidAccountException e) { System.out.println("Error: " + e.getMessage()); }
finally { System.out.println("Transaction attempt completed."); }
  • throw raises an exception object; throws in the header warns callers.
  • finally runs whether or not an exception happened — use it for cleanup and logging.

Theory 5 of 5

Custom exceptions and wrapping

  • Extend Exception (checked) or RuntimeException (unchecked) and pass the message to super.
  • Wrapping turns a low-level error into one meaningful type; getCause() still returns the original.

Custom exceptions and wrapping

} catch (InsufficientFundsException e) {
    throw new TransactionException("Transfer failed.", e);   // keep the cause
}

Implementation design

Execution flow

  1. Create two valid accounts inside try-catch
  2. Try invalid accounts (empty number, negative balance) and catch the checked exception
  3. Withdraw too much and catch the unchecked exception
  4. Do valid deposits and withdrawals
  5. Show that finally always runs
  6. Transfer successfully, then fail a transfer and read the wrapped cause
  7. Reject negative amounts and print the final balances

Three custom exceptions make each failure self-explaining; transfer() wraps lower-level failures.

Code walkthrough 1 of 10 · InvalidAccountException.java

A custom checked exception

public class InvalidAccountException extends Exception {

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

    public InvalidAccountException(String message, Throwable cause) {
        super(message, cause);
    }
}
  • Extends Exception, so callers must catch or declare it.
  • The second constructor keeps a cause.

Code walkthrough 2 of 10 · InsufficientFundsException.java

An unchecked one

public class InsufficientFundsException extends RuntimeException {

    public InsufficientFundsException(String message) {
        super(message);
    }
  • Extends RuntimeException: the compiler does not force handling.

Code walkthrough 3 of 10 · BankAccount.java

Validate in the constructor

public BankAccount(String accountNumber, String owner, double initialBalance)
        throws InvalidAccountException {

    if (accountNumber == null || accountNumber.trim().isEmpty()) {
        throw new InvalidAccountException("Account number cannot be null or empty.");
    }
    if (owner == null || owner.trim().isEmpty()) {
        throw new InvalidAccountException("Owner name cannot be null or empty.");
    }
    if (initialBalance < 0.0) {
        throw new InvalidAccountException(

Code walkthrough 3 of 10 · BankAccount.java

Validate in the constructor (cont.)

                "Initial balance cannot be negative. Provided: " + initialBalance);
    }
    this.accountNumber = accountNumber;
    this.owner = owner;
    this.balance = initialBalance;
    totalAccounts++;
}
  • throw stops construction, so invalid accounts are never counted.
  • throws in the header warns callers.

Code walkthrough 4 of 10 · BankAccount.java

deposit() rejects bad amounts

public void deposit(double amount) throws InvalidAccountException {
    if (amount <= 0.0) {
        throw new InvalidAccountException(
                "Deposit amount must be positive. Provided: " + amount);
    }
    balance = balance + amount;
    System.out.println("[" + accountNumber + "] Deposit: " + amount
            + " | New balance: " + balance);
}
  • A non-positive amount → the checked InvalidAccountException.

Code walkthrough 5 of 10 · BankAccount.java

withdraw() can fail in two ways

public void withdraw(double amount) throws InvalidAccountException {
    if (amount <= 0.0) {
        throw new InvalidAccountException(
                "Withdrawal amount must be positive. Provided: " + amount);
    }
    if (amount > balance) {
        throw new InsufficientFundsException(
                "Insufficient funds. Requested: " + amount + ", Available: " + balance);
    }
    balance = balance - amount;
    System.out.println("[" + accountNumber + "] Withdrawal: " + amount
            + " | New balance: " + balance);
}
  • Bad amount → checked exception (declared in throws).
  • Too little money → unchecked exception (no declaration needed).

Code walkthrough 6 of 10 · BankAccount.java

transfer() wraps low-level failures

public void transfer(BankAccount target, double amount) throws TransactionException {
    if (target == null) {
        throw new TransactionException("Transfer failed: target account is null.");
    }
    try {
        System.out.println("--- Starting transfer ---");
        System.out.println("From: " + this.accountNumber + " | To: "
                + target.accountNumber + " | Amount: " + amount);
        this.withdraw(amount);
        target.deposit(amount);
        System.out.println("Transfer completed successfully.");

Code walkthrough 6 of 10 · BankAccount.java

transfer() wraps low-level failures (cont.)

    } catch (InsufficientFundsException e) {
        throw new TransactionException(
                "Transfer failed: insufficient funds in source account.", e);
    } catch (InvalidAccountException e) {
        throw new TransactionException(
                "Transfer failed: invalid transaction amount.", e);
    }
}
  • Both failures become one TransactionException; e is kept as the cause.

Code walkthrough 7 of 10 · Lab6Demo.java

Catching a checked exception

// 2. Invalid Account Creation
System.out.println("\n=== Invalid Account Creation ===");
try {
    BankAccount invalidAccount = new BankAccount("", "Charlie", 1000.0);
} catch (InvalidAccountException e) {
    System.out.println("Caught checked exception: " + e.getMessage());
}
try {
    BankAccount invalidAccount = new BankAccount("ACC003", "Diana", -500.0);
} catch (InvalidAccountException e) {
    System.out.println("Caught checked exception: " + e.getMessage());
}
  • The constructor throws, the catch block reports it, the program continues.

Code walkthrough 8 of 10 · Lab6Demo.java

Two catch blocks for one call

// 3. Insufficient Funds
System.out.println("\n=== Insufficient Funds Example ===");
try {
    account1.withdraw(10000.0);
} catch (InsufficientFundsException e) {
    System.out.println("Caught unchecked exception: " + e.getMessage());
} catch (InvalidAccountException e) {
    System.out.println("Caught checked exception: " + e.getMessage());
}
  • withdraw() can throw either exception; Java runs the first matching catch.

Code walkthrough 9 of 10 · Lab6Demo.java

finally always runs

// 5. try-catch-finally
System.out.println("\n=== try-catch-finally Example ===");
try {
    System.out.println("Attempting to withdraw 500 from account1...");
    account1.withdraw(500.0);
    System.out.println("Withdrawal successful.");
} catch (InsufficientFundsException e) {
    System.out.println("Caught InsufficientFundsException: " + e.getMessage());
} catch (InvalidAccountException e) {
    System.out.println("Caught InvalidAccountException: " + e.getMessage());
} finally {
    System.out.println("Finally block: cleanup or logging code runs.");
    account1.display();
}
  • Success or failure, the finally block prints and shows the account.

Code walkthrough 10 of 10 · Lab6Demo.java

Reading the wrapped cause

// 7. Failed Transfer
System.out.println("\n=== Failed Transfer ===");
try {
    account2.transfer(account1, 5000.0);
} catch (TransactionException e) {
    System.out.println("Caught TransactionException: " + e.getMessage());
    System.out.println("Root cause: " + e.getCause().getMessage());
}
  • getCause() returns the original InsufficientFundsException.

Step through the code · 1 of 3

Trace: a constructor that throws

Pick an input. An invalid account is never created and never counted.

Step through the code · 1 of 3

Trace: a constructor that throws (cont.)

Step through the code · 2 of 3

Trace: withdraw, catch and finally

Pick an amount and follow the exception (if any) to its catch block.

Step through the code · 2 of 3

Trace: withdraw, catch and finally (cont.)

Step through the code · 3 of 3

Trace: wrapping an exception with its cause

A low-level InsufficientFundsException becomes a TransactionException. Follow the cause arrow back to the original.

Step through the code · 3 of 3

Trace: wrapping an exception with its cause (cont.)

For reference · 5 files

The complete program

public class InvalidAccountException extends Exception {

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

    public InvalidAccountException(String message, Throwable cause) {
        super(message, cause);
    }
}
public class InsufficientFundsException extends RuntimeException {

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

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

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

    public TransactionException(String message, Throwable cause) {
        super(message, cause);
    }
}
public class BankAccount {

    private String accountNumber;
    private String owner;
    private double balance;

    private static int totalAccounts = 0;

    public BankAccount(String accountNumber, String owner, double initialBalance)
            throws InvalidAccountException {

        if (accountNumber == null || accountNumber.trim().isEmpty()) {
            throw new InvalidAccountException("Account number cannot be null or empty.");
        }
        if (owner == null || owner.trim().isEmpty()) {
            throw new InvalidAccountException("Owner name cannot be null or empty.");
        }
        if (initialBalance < 0.0) {
            throw new InvalidAccountException(
                    "Initial balance cannot be negative. Provided: " + initialBalance);
        }
        this.accountNumber = accountNumber;
        this.owner = owner;
        this.balance = initialBalance;
        totalAccounts++;
    }

    public void deposit(double amount) throws InvalidAccountException {
        if (amount <= 0.0) {
            throw new InvalidAccountException(
                    "Deposit amount must be positive. Provided: " + amount);
        }
        balance = balance + amount;
        System.out.println("[" + accountNumber + "] Deposit: " + amount
                + " | New balance: " + balance);
    }

    public void withdraw(double amount) throws InvalidAccountException {
        if (amount <= 0.0) {
            throw new InvalidAccountException(
                    "Withdrawal amount must be positive. Provided: " + amount);
        }
        if (amount > balance) {
            throw new InsufficientFundsException(
                    "Insufficient funds. Requested: " + amount + ", Available: " + balance);
        }
        balance = balance - amount;
        System.out.println("[" + accountNumber + "] Withdrawal: " + amount
                + " | New balance: " + balance);
    }

    public void transfer(BankAccount target, double amount) throws TransactionException {
        if (target == null) {
            throw new TransactionException("Transfer failed: target account is null.");
        }
        try {
            System.out.println("--- Starting transfer ---");
            System.out.println("From: " + this.accountNumber + " | To: "
                    + target.accountNumber + " | Amount: " + amount);
            this.withdraw(amount);
            target.deposit(amount);
            System.out.println("Transfer completed successfully.");
        } catch (InsufficientFundsException e) {
            throw new TransactionException(
                    "Transfer failed: insufficient funds in source account.", e);
        } catch (InvalidAccountException e) {
            throw new TransactionException(
                    "Transfer failed: invalid transaction amount.", e);
        }
    }

    public String getAccountNumber() { return accountNumber; }
    public String getOwner() { return owner; }
    public double getBalance() { return balance; }
    public static int getTotalAccounts() { return totalAccounts; }

    public void display() {
        System.out.println("Account: " + accountNumber + " | Owner: " + owner
                + " | Balance: " + balance);
    }
}
public class Lab6Demo {

    public static void main(String[] args) {

        // 1. Creating Bank Accounts
        System.out.println("=== Creating Bank Accounts ===");
        BankAccount account1 = null;
        BankAccount account2 = null;
        try {
            account1 = new BankAccount("ACC001", "Alice", 5000.0);
            System.out.println("Account 1 created successfully.");
        } catch (InvalidAccountException e) {
            System.out.println("Error creating account 1: " + e.getMessage());
        }
        try {
            account2 = new BankAccount("ACC002", "Bob", 3000.0);
            System.out.println("Account 2 created successfully.");
        } catch (InvalidAccountException e) {
            System.out.println("Error creating account 2: " + e.getMessage());
        }

        // 2. Invalid Account Creation
        System.out.println("\n=== Invalid Account Creation ===");
        try {
            BankAccount invalidAccount = new BankAccount("", "Charlie", 1000.0);
        } catch (InvalidAccountException e) {
            System.out.println("Caught checked exception: " + e.getMessage());
        }
        try {
            BankAccount invalidAccount = new BankAccount("ACC003", "Diana", -500.0);
        } catch (InvalidAccountException e) {
            System.out.println("Caught checked exception: " + e.getMessage());
        }

        // 3. Insufficient Funds
        System.out.println("\n=== Insufficient Funds Example ===");
        try {
            account1.withdraw(10000.0);
        } catch (InsufficientFundsException e) {
            System.out.println("Caught unchecked exception: " + e.getMessage());
        } catch (InvalidAccountException e) {
            System.out.println("Caught checked exception: " + e.getMessage());
        }

        // 4. Valid Deposit and Withdrawal
        System.out.println("\n=== Valid Transactions ===");
        try {
            account1.display();
            account1.deposit(2000.0);
            account1.withdraw(1500.0);
            account1.display();
        } catch (InvalidAccountException e) {
            System.out.println("Transaction error: " + e.getMessage());
        }

        // 5. try-catch-finally
        System.out.println("\n=== try-catch-finally Example ===");
        try {
            System.out.println("Attempting to withdraw 500 from account1...");
            account1.withdraw(500.0);
            System.out.println("Withdrawal successful.");
        } catch (InsufficientFundsException e) {
            System.out.println("Caught InsufficientFundsException: " + e.getMessage());
        } catch (InvalidAccountException e) {
            System.out.println("Caught InvalidAccountException: " + e.getMessage());
        } finally {
            System.out.println("Finally block: cleanup or logging code runs.");
            account1.display();
        }

        // 6. Successful Transfer
        System.out.println("\n=== Successful Transfer ===");
        try {
            account1.transfer(account2, 1000.0);
        } catch (TransactionException e) {
            System.out.println("Caught TransactionException: " + e.getMessage());
            System.out.println("Cause: " + e.getCause().getMessage());
        }

        // 7. Failed Transfer
        System.out.println("\n=== Failed Transfer ===");
        try {
            account2.transfer(account1, 5000.0);
        } catch (TransactionException e) {
            System.out.println("Caught TransactionException: " + e.getMessage());
            System.out.println("Root cause: " + e.getCause().getMessage());
        }

        // 8. Invalid Transaction Amounts
        System.out.println("\n=== Invalid Transaction Amounts ===");
        try {
            account1.deposit(-100.0);
        } catch (InvalidAccountException e) {
            System.out.println("Caught deposit error: " + e.getMessage());
        }
        try {
            account1.withdraw(-50.0);
        } catch (InsufficientFundsException e) {
            System.out.println("Caught insufficient funds error: " + e.getMessage());
        } catch (InvalidAccountException e) {
            System.out.println("Caught withdrawal error: " + e.getMessage());
        }

        // 9. Final Account States
        System.out.println("\n=== Final Account States ===");
        account1.display();
        account2.display();
        System.out.println("Total accounts created: " + BankAccount.getTotalAccounts());
    }
}

Copy or download each file, save it under the same name, then compile and run: javac *.java → java Lab6Demo

Implementation

Expected output

Only 2 accounts are counted: the constructor throws before totalAccounts++ for the invalid ones.

Interactive 1 of 1

Which catch block handles it?

In-lab task 1 of 3

Task 1: Add a FrozenAccountException

Accounts can be frozen; a frozen account rejects all money movement.
Requirements
Show a hint
Check the frozen flag first in each method, before validating the amount.

In-lab task 2 of 3

Task 2: Amount validation utility

One static method validates every amount.
Requirements
Show a hint
Because it throws a checked exception, its header needs throws InvalidAccountException.

In-lab task 3 of 3

Task 3: Transaction history

Record what actually happened to an account.
Requirements
Show a hint
Add the record as the last line of each method: if an exception is thrown earlier, that line never runs.

In-lab tasks

Show your work during the lab

For each task, show the instructor:

  • the source code
  • a successful compilation
  • the running output
  • a short explanation of the logic you used

Lab report · exercise 1 of 2

Robust CSV file processing

Read student records (ID,Name,GPA) from a CSV file with custom exceptions.
Requirements

Lab report · exercise 2 of 2

E-commerce order processing

Product, ShoppingCart and Order with exception chaining.
Requirements

Lab report

Writing the lab report

  • Problem statement and your design (classes, fields, methods)
  • Complete source code, formatted and commented
  • Sample output or screenshots for normal and edge cases
  • A short discussion: what each OOP concept did in your program

Academic integrity

Copying from the internet, classmates, seniors or any other unauthorized source is strictly prohibited. Full marks may be deducted if plagiarism, copied work or academic dishonesty is detected. Complete the tasks, implementation, output analysis and report yourself.

Discussion and analysis

Viva questions (1 of 2)

Why must new BankAccount(…) be inside try-catch here?
Show the answer
Its constructor declares the checked InvalidAccountException, so the compiler requires the caller to catch or declare it.
Why is InsufficientFundsException unchecked?
Show the answer
It is a runtime business-rule violation; making it unchecked keeps callers free to handle it where it makes sense.

Discussion and analysis

Viva questions (2 of 2)

When does finally not matter?
Show the answer
It always runs after try/catch (even after a return), except when the JVM exits — so it is the right place for cleanup.
What does exception wrapping give the caller?
Show the answer
One meaningful exception type to catch (TransactionException) while getCause() still reveals the original problem.

Check yourself

Quiz

Summary

Summary

  • Exceptions interrupt normal flow; catch them to recover with a clear message.
  • Checked exceptions must be caught or declared; unchecked ones need not be.
  • throw raises, throws declares, finally always runs.
  • Custom exceptions name domain errors.
  • Wrap low-level exceptions to give callers one meaningful type, keeping the cause.