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
throwand declare them withthrows - 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
- Theory recap
- Walk through the program, section by section, and run it
- In-lab tasks, shown to the instructor
- Report exercises at home
Lab 6 of 10
Contents
Select a part to jump to it.
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
catchhandles 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.RuntimeExceptionand its subclasses are unchecked.
Theory 3 of 5
Checked vs unchecked
| Checked | Unchecked | |
|---|---|---|
| Extends | Exception | RuntimeException |
| Compiler | must catch or declare | not enforced |
| Typical cause | outside problem (file, bad account data) | programming error / business rule |
| In this lab | InvalidAccountException, TransactionException | InsufficientFundsException |
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."); }throwraises an exception object;throwsin the header warns callers.finallyruns whether or not an exception happened — use it for cleanup and logging.
Theory 5 of 5
Custom exceptions and wrapping
- Extend
Exception(checked) orRuntimeException(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
- Create two valid accounts inside try-catch
- Try invalid accounts (empty number, negative balance) and catch the checked exception
- Withdraw too much and catch the unchecked exception
- Do valid deposits and withdrawals
- Show that finally always runs
- Transfer successfully, then fail a transfer and read the wrapped cause
- 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++;
}throwstops construction, so invalid accounts are never counted.throwsin 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;
eis 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
=== Creating Bank Accounts === Account 1 created successfully. Account 2 created successfully. === Invalid Account Creation === Caught checked exception: Account number cannot be null or empty. Caught checked exception: Initial balance cannot be negative. Provided: -500.0 === Insufficient Funds Example === Caught unchecked exception: Insufficient funds. Requested: 10000.0, Available: 5000.0 === Valid Transactions === Account: ACC001 | Owner: Alice | Balance: 5000.0 [ACC001] Deposit: 2000.0 | New balance: 7000.0 [ACC001] Withdrawal: 1500.0 | New balance: 5500.0 Account: ACC001 | Owner: Alice | Balance: 5500.0 === try-catch-finally Example === Attempting to withdraw 500 from account1... [ACC001] Withdrawal: 500.0 | New balance: 5000.0 Withdrawal successful. Finally block: cleanup or logging code runs. Account: ACC001 | Owner: Alice | Balance: 5000.0 === Successful Transfer === --- Starting transfer --- From: ACC001 | To: ACC002 | Amount: 1000.0 [ACC001] Withdrawal: 1000.0 | New balance: 4000.0 [ACC002] Deposit: 1000.0 | New balance: 4000.0 Transfer completed successfully. === Failed Transfer === --- Starting transfer --- From: ACC002 | To: ACC001 | Amount: 5000.0 Caught TransactionException: Transfer failed: insufficient funds in source account. Root cause: Insufficient funds. Requested: 5000.0, Available: 4000.0 === Invalid Transaction Amounts === Caught deposit error: Deposit amount must be positive. Provided: -100.0 Caught withdrawal error: Withdrawal amount must be positive. Provided: -50.0 === Final Account States === Account: ACC001 | Owner: Alice | Balance: 4000.0 Account: ACC002 | Owner: Bob | Balance: 4000.0 Total accounts created: 2
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
Show a hint
In-lab task 2 of 3
Task 2: Amount validation utility
Show a hint
throws InvalidAccountException.In-lab task 3 of 3
Task 3: Transaction history
Show a hint
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
Lab report · exercise 2 of 2
E-commerce order processing
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)
new BankAccount(…) be inside try-catch here?Show the answer
Show the answer
Discussion and analysis
Viva questions (2 of 2)
Show the answer
Show the answer
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.