Skip to lab content

WEEK 8 · OBJECT ORIENTED PROGRAMMING LAB

ATM withdrawal with a checked exception

EXPERIMENT 15Java 11+Console program

THE SYLLABUS TASK

What you need to build

Develop an ATM withdrawal simulation with balance validation.
Full syllabus requirements: classes, fields and methods
Develop an ATM withdrawal simulation with balance validation. Class: ATMAccount Attributes: accountNumber, holderName, balance Methods Required: • withdraw(double amount) throws InsufficientFundsException • deposit(double amount) • checkBalance() Generate InsufficientFundsException and handle in a try-catch block.
Input format & conventions

Account number, holder and opening balance; then 1 amount deposit, 2 amount withdraw, 3 balance, 0 exit. One field per line; positive finite transaction amounts only.

UNDERSTAND THE IDEA

Explanation

InsufficientFundsException extends Exception, making insufficient funds a checked condition that withdraw declares with throws. The caller catches it around the transaction loop and can continue with another command.

The account checks available balance before changing state. Invalid amount formats or nonpositive values are separate input-contract errors. A denied withdrawal leaves balance unchanged.

TURN THE IDEA INTO STEPS

Step-by-step algorithm

  1. Create the account.
  2. Validate an operation amount.
  3. Throw InsufficientFundsException if withdrawal exceeds balance.
  4. Catch and report that condition without changing the balance.
  5. Continue processing valid account commands.

SEE THE CORE OPERATION

Flowchart

This chart focuses on the named operation or core OOP decision. Diamonds show conditions; labeled branches show outcomes. Read it with the complete algorithm and source for input handling and other operations. Scroll within the diagram on a phone.

Custom exception for funds

Custom exception for funds

Open full-size flowchart ↗

Java 11+

Complete Java source

Main.java

Download .java
import java.util.*;

public class Main {
    private static final Scanner IN = new Scanner(System.in).useLocale(Locale.ROOT);
    static String text() {
        if (!IN.hasNextLine()) throw new IllegalArgumentException("Incomplete input.");
        String value = IN.nextLine().trim();
        if (value.isEmpty() || value.length() > 200) throw new IllegalArgumentException("Text must have 1–200 characters.");
        return value;
    }
    static int integer(int low, int high) {
        int value = Integer.parseInt(text());
        if (value < low || value > high) throw new IllegalArgumentException("Integer out of range.");
        return value;
    }
    static double number(double low, double high) {
        double value = Double.parseDouble(text());
        if (!Double.isFinite(value) || value < low || value > high) throw new IllegalArgumentException("Number out of range.");
        return value;
    }
    static void money(String label, double value) { System.out.printf(Locale.ROOT, "%s: %.2f%n", label, value); }
static class InsufficientFundsException extends Exception {
        private static final long serialVersionUID=1L;
        InsufficientFundsException(String message){super(message);}
    }
    static class ATMAccount {
        final String accountNumber,holderName; private double balance;
        ATMAccount(String number,String name,double balance){accountNumber=number;holderName=name;this.balance=balance;}
        void withdraw(double amount) throws InsufficientFundsException {
            if(!Double.isFinite(amount)||amount<=0)throw new IllegalArgumentException("Amount must be positive.");
            if(amount>balance)throw new InsufficientFundsException("Insufficient funds; balance unchanged.");balance-=amount;
        }
        void deposit(double amount){
            if(!Double.isFinite(amount)||amount<=0||balance+amount>1000000000)throw new IllegalArgumentException("Invalid deposit.");balance+=amount;
        }
        double checkBalance(){return balance;}
    }
    // account-number/holder/opening-balance, then 1 amount=deposit, 2 amount=withdraw,
    // 3=balance, 0=exit; each value on its own line.
    public static void main(String[] args){
        try{
            ATMAccount account=new ATMAccount(text(),text(),number(0,1000000));
            while(true){
                int command=integer(0,3);if(command==0)break;
                try{
                    if(command==1){account.deposit(number(0,1000000));System.out.println("Deposited.");}
                    else if(command==2){account.withdraw(number(0,1000000));System.out.println("Withdrawn.");}
                    else money("Balance",account.checkBalance());
                }catch(InsufficientFundsException e){System.out.println(e.getMessage());}
            }
        }catch(IllegalArgumentException e){System.out.println("Invalid input: "+e.getMessage());}
    }
}

Copy a source file or download the project to preserve its file names.

USE THE RIGHT ENVIRONMENT

How to compile and run

Install a JDK (Java 11 or newer; JDK 17 is suitable), extract this project’s ZIP into its own folder, then open a terminal in that folder.

javac -encoding UTF-8 Main.java
java Main

The standalone source is named Main.java and its entry class is Main. You may also paste it into an online Java compiler and supply the sample input there. Choose Java explicitly; these pages do not change the site compiler or import code automatically.

Each experiment has its own folder because many use the public class Main. Compile and run one experiment at a time.

UNDERSTAND THE JAVA CODE

Line-by-line explanation

These steps follow the exact Java source above. Related statements are grouped; line numbers count blank lines and restart for each file. Start with main to follow execution, then open the constructors, methods or event handlers it uses.

Start with main →

Main.java

Lines 1–4Import utilities and create the shared input reader
import java.util.*;

public class Main {
    private static final Scanner IN = new Scanner(System.in).useLocale(Locale.ROOT);

java.util.* imports utility type names such as Scanner, Locale and collections. Main is the public class matching Main.java. The private static final IN field holds one shared Scanner for System.in; final prevents replacing that reference. The scanner uses Locale.ROOT.

Lines 5–10Read one nonempty trimmed input line
    static String text() {
        if (!IN.hasNextLine()) throw new IllegalArgumentException("Incomplete input.");
        String value = IN.nextLine().trim();
        if (value.isEmpty() || value.length() > 200) throw new IllegalArgumentException("Text must have 1–200 characters.");
        return value;
    }

text checks whether a line exists, reads it and removes leading/trailing whitespace. Reject empty text and lengths above 200 by throwing IllegalArgumentException. Otherwise return the string. One field is read per line, so these helpers do not mix nextInt with leftover newlines.

Lines 11–15Parse and range-check an integer field
    static int integer(int low, int high) {
        int value = Integer.parseInt(text());
        if (value < low || value > high) throw new IllegalArgumentException("Integer out of range.");
        return value;
    }

Integer.parseInt converts the returned string to int or throws NumberFormatException, a subtype of IllegalArgumentException. Reject values outside the caller’s inclusive low/high limits, then return the accepted integer.

Lines 16–20Parse and range-check a finite decimal field
    static double number(double low, double high) {
        double value = Double.parseDouble(text());
        if (!Double.isFinite(value) || value < low || value > high) throw new IllegalArgumentException("Number out of range.");
        return value;
    }

Double.parseDouble reads the string as a double. Double.isFinite rejects NaN and infinities as well as the explicit low/high range check. Return only a finite accepted value; exceptions transfer control to a matching catch block.

Line 21Format a labelled numeric result consistently
    static void money(String label, double value) { System.out.printf(Locale.ROOT, "%s: %.2f%n", label, value); }

money is a formatting helper: %s prints the label, %.2f prints two decimal places, and %n ends the line. Locale.ROOT fixes locale-sensitive formatting. The helper can also display non-currency quantities; it does not change the stored value.

Lines 22–25Define a checked insufficient-funds exception
static class InsufficientFundsException extends Exception {
        private static final long serialVersionUID=1L;
        InsufficientFundsException(String message){super(message);}
    }

The exception extends Exception and accepts a message passed to the parent constructor. The account method declares this checked failure explicitly.

Lines 26–28Store account identity and its current balance
    static class ATMAccount {
        final String accountNumber,holderName; private double balance;
        ATMAccount(String number,String name,double balance){accountNumber=number;holderName=name;this.balance=balance;}

ATMAccount has final identity strings and a private editable balance. The driver checks the initial balance before constructing the object.

Lines 29–32Check both kinds of withdrawal failure before deduction
        void withdraw(double amount) throws InsufficientFundsException {
            if(!Double.isFinite(amount)||amount<=0)throw new IllegalArgumentException("Amount must be positive.");
            if(amount>balance)throw new InsufficientFundsException("Insufficient funds; balance unchanged.");balance-=amount;
        }

A bad amount throws IllegalArgumentException. An amount exceeding balance throws InsufficientFundsException. Only after both checks pass is balance reduced, so an insufficient-funds attempt leaves it unchanged.

Lines 33–37Provide deposit and balance operations
        void deposit(double amount){
            if(!Double.isFinite(amount)||amount<=0||balance+amount>1000000000)throw new IllegalArgumentException("Invalid deposit.");balance+=amount;
        }
        double checkBalance(){return balance;}
    }

deposit validates the amount and resulting balance before adding. checkBalance returns the current balance without changing it. The last brace ends ATMAccount.

Lines 38–44Read the account and start its command session
    // account-number/holder/opening-balance, then 1 amount=deposit, 2 amount=withdraw,
    // 3=balance, 0=exit; each value on its own line.
    public static void main(String[] args){
        try{
            ATMAccount account=new ATMAccount(text(),text(),number(0,1000000));
            while(true){
                int command=integer(0,3);if(command==0)break;

Input uses one field per line. Construct the account from checked data, then read commands 0–3. Exit breaks the loop; another command enters the nested try block.

Lines 45–50Catch insufficient funds inside the loop so the session continues
                try{
                    if(command==1){account.deposit(number(0,1000000));System.out.println("Deposited.");}
                    else if(command==2){account.withdraw(number(0,1000000));System.out.println("Withdrawn.");}
                    else money("Balance",account.checkBalance());
                }catch(InsufficientFundsException e){System.out.println(e.getMessage());}
            }

Deposit, withdraw or display according to the command. Catch InsufficientFundsException around just this iteration. After its message the loop can read another command instead of ending the entire program.

Lines 51–53Report invalid input and close the enclosing scopes
        }catch(IllegalArgumentException e){System.out.println("Invalid input: "+e.getMessage());}
    }
}

The catch handles IllegalArgumentException, including invalid numeric strings from the helpers, and prints its message. The final braces close main and Main. Java objects become eligible for garbage collection when no live reference needs them; this program does not manually free them.

FOLLOW THE STATE

Dry run

  1. Opening balance is 100.
  2. Withdrawing 200 throws the custom exception before deduction.
  3. The catch prints the denial message.
  4. A following balance command still reports 100.

CHECK THE BEHAVIOUR

Sample input & output

These outputs were captured by running the exact delivered Java project.

Sample 1

INPUT
001
Asha
100
2
200
3
1
50
2
100
3
0
OUTPUT
Insufficient funds; balance unchanged.
Balance: 100.00
Deposited.
Withdrawn.
Balance: 50.00

Sample 2

INPUT
001
Asha
100
2
100
3
0
OUTPUT
Withdrawn.
Balance: 0.00

Common mistakes

  • Declare the checked exception in withdraw’s signature.
  • Throw before modifying balance.
  • Catch insufficient funds near the operation so the menu can continue.

WHY THIS GROWTH RATE?

Time and space complexity

O(1) account state work per transaction; O(q) for q commands, plus parsing/output.

Balance checks and updates touch one numeric field. The example therefore has constant successful-operation work.

Exception construction and stack-trace capture involve runtime overhead; the simple bound treats call depth as fixed here. It does not imply exceptions are cost-free.

These bounds describe the implemented data processing. Input/output text length, scheduling, framework rendering and runtime/library internals are identified separately where they matter. Educational limits bound the actual examples.

PREPARE FOR YOUR LAB VIVA

Related viva questions & answers

6 focused questions

Try answering aloud, then expand to check your reasoning. Questions focus on this program’s classes, state and behavior.

Why is InsufficientFundsException a checked exception?

It extends Exception rather than RuntimeException. A caller must catch it or declare it in its own throws clause.

What does throws in the withdraw signature communicate?

It tells callers that this operation may fail with InsufficientFundsException. The declaration is separate from the actual throw executed when funds are insufficient.

Why is the insufficient-funds catch inside the menu loop?

That condition is a recoverable transaction failure in this demonstration. Catching it locally allows the user to request another operation afterward.

Does a denied withdrawal change the account balance?

No. The exception is thrown before subtraction, so the account retains its previous state.

Why are invalid amounts not reported as insufficient funds?

A nonpositive or nonfinite amount violates the operation’s input contract. That differs from a valid requested amount exceeding available funds.

Why does the custom exception include serialVersionUID?

Exception classes are serializable. Declaring the field avoids a serialization lint warning and makes the class’s version identifier explicit.