try, catch and finally

Whatever happens at an ATM, it gives your card back. try, catch and finally let your code behave the same way: try the risky work, catch the problems you can handle, and finally clean up no matter what. You'll see exactly where control jumps, why catch order matters, how multi-catch saves code, the rare cases where finally doesn't run, and then practise with 11 problems.

Module 9 · Exception Handling⏱ 24 minIntermediate11 practice problems
1 The story

The ATM that always gives the card back

Riya tries to withdraw Rs 900 at the campus ATM. The screen says “Insufficient balance”. Annoying, but the machine still pushes her card out. Back in the lab she realises her own banking program does not do that: when a withdrawal fails, it skips the “return card” line along with everything else.

Insufficient balance card comes out trywithdraw(900) catchshow the message finallyreturn card only if it fails every path ends here myphatshala.in
Riya

Sir, when the withdrawal throws an exception, my “return card” line is skipped. If I copy it into the catch block too, it feels wrong.

Arjun Sir

It is wrong: you'd have to copy it into every path, and one day you'd forget one. Put it in a finally block. Java runs finally after the try, whether the try succeeded, failed and was caught, or failed and wasn't caught at all.

Riya

So try is the risky work, catch is the plan B, and finally is the “no matter what” part?

Arjun Sir

Exactly. And when you have several plan Bs, the order of the catch blocks matters. Let's see why.

2 Definition

What are try, catch and finally?

Formal definition (write this in exams)

A try statement consists of a try block followed by zero or more catch clauses and an optional finally block (at least one catch or finally is required, unless it declares resources). If an exception is thrown in the try block, the first catch clause whose parameter type is the exception's class or a superclass of it is executed. The finally block is executed after the try and any catch block complete, whether they complete normally or abruptly (by an exception, return, break or continue).

In simple words

try: “attempt this.” catch: “if that kind of problem happens, do this instead.” finally: “and whatever happened, always do this at the end.”

Words you'll meet in this lesson

try blockThe code that might throw. It stops at the first exception.
catch clauseA handler for one type of exception (and its subclasses).
Catch parameterThe variable, like e, that receives the exception object.
finally blockClean-up code that runs on every path out of the try.
Multi-catchOne catch for several types: catch (A | B e). Java 7+.
Abrupt completionLeaving a block early through an exception, return, break or continue.

The rules at a glance

Valid shapestry-catch, try-finally, try-catch-finally, try-with-resources (Lesson 9.6). A try alone is a compile error.
Which catch runs?The first one, top to bottom, whose type matches. At most one catch runs per exception.
Order of catchesSubclass before superclass. A superclass first makes the later one unreachable: compile error.
Multi-catchcatch (NumberFormatException | ArithmeticException e). Types must not be related; e is implicitly final.
finally runs…after a normal try, after a catch, when nothing matches, and even after return, break, continue
finally does NOT runif System.exit() is called, or the JVM crashes or is killed first

Exam tip: “What prints?” questions love try { return 1; } finally { return 2; }. The method returns 2: a return in finally replaces the pending one. And if finally only changes a variable after return x;, the old value is still returned.

3 Types and ways to use it

Three shapes, three paths

try-catch

Handle a problem and carry on. Use when you can do something useful: use a default, show a message, skip a record.

Recover

try-catch-finally

Handle the problem and also clean up on every path: return the card, unlock the record, stop the timer.

Recover + clean up

try-finally

Don't handle it here; let it travel to the caller, but clean up first. The finally runs, then the exception continues.

Clean up, pass on

Where control goes: no exception, caught, not caught

public class Main {
    static void process(String qty) {
        try {
            System.out.println("  try: parsing " + qty);
            int q = Integer.parseInt(qty);
            System.out.println("  try: 100 / " + q + " = " + (100 / q));
        } catch (NumberFormatException e) {
            System.out.println("  catch: not a number");
        } finally {
            System.out.println("  finally: always runs");
        }
        System.out.println("  after the try statement");
    }

    public static void main(String[] args) {
        String[] inputs = {"4", "abc", "0"};
        for (String in : inputs) {
            System.out.println("Input " + in + ":");
            try {
                process(in);
            } catch (ArithmeticException e) {
                System.out.println("  main caught: " + e.getMessage());
            }
        }
    }
}
Input 4: try: parsing 4 try: 100 / 4 = 25 finally: always runs after the try statement Input abc: try: parsing abc catch: not a number finally: always runs after the try statement Input 0: try: parsing 0 finally: always runs main caught: / by zero
SituationRest of trycatchfinallyCode after the try statement
No exception ("4")RunsSkippedRunsRuns
Exception caught ("abc")SkippedMatching one runsRunsRuns
No matching catch ("0")SkippedNone runsRunsSkipped: the exception goes to the caller

The return-in-finally trap: a return (or throw) inside finally replaces whatever was pending, including an exception. try { throw new IllegalStateException("Card declined"); } finally { return "Payment OK"; } returns "Payment OK" and the exception vanishes without a trace. Use finally only for clean-up, never to return values. javac -Xlint:finally warns: “finally clause cannot complete normally”.

4 Step by step

Writing a safe block, start to finish

  1. Wrap only the risky work in try

    try {
        withdraw(amount);
        System.out.println("Take your cash");
    }
    javac records the bytecode range of the try block in the method's exception table. Code outside that range is not protected. Keep try blocks small so it is clear which line can fail.
  2. Add catch blocks, most specific first

    catch (NumberFormatException e) { … }   // subclass first
    catch (IllegalArgumentException e) { … } // its parent after
    Each catch becomes a row in the exception table, in source order. The JVM takes the first row whose type matches. If the parent came first, the child's row could never be reached, so javac rejects it: exception NumberFormatException has already been caught.
  3. Same handling? Use multi-catch

    catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
        System.out.println("Bad line: " + e);
    }
    One handler, several exception-table rows pointing to the same code. The types must be unrelated (otherwise: Alternatives in a multi-catch statement cannot be related by subclassing) and e cannot be reassigned.
  4. Put must-happen work in finally

    finally {
        System.out.println("Card returned");
    }
    javac copies the finally code onto every exit path: after the try, after each catch, and into a hidden “any” handler that runs it and then rethrows the exception. You can see the copies with javap -c.
  5. Know the exceptions to “always”

    try {
        System.exit(0);       // JVM shuts down here
    } finally {
        System.out.println("never printed");
    }
    System.exit starts JVM shutdown and never returns, so control never reaches the finally code. A power cut, a killed process or a JVM crash also skip it. For real resources, the operating system frees what it can when the process ends.
5 See it happen

From try to catch to finally: both views

Left: the line that runs and the console. Right: which block is active, the exception table the JVM checks, and the exception object.

An ATM request of Rs 900 with only Rs 600 in the account 6 steps
What you see try { withdraw(900); throw new IllegalStateException(…) println("Take your cash"); // skipped catch (IllegalStateException e) finally { println("Card returned"); } // next request: Rs 500 Request: Rs 900 Declined: Insufficient balance Card returned Request: Rs 500 The risky call runs inside try. withdraw finds too little money and throws. The rest of the try block is skipped. The matching catch shows the reason. finally runs: the card comes back. Handled, so the program carries on. myphatshala.in
Behind the screen try withdraw(900); cash catch (IllegalState…) finally: card after: next request #1 IllegalStateException "Insufficient balance" withdraw frame on the stack #1 unreachable: garbage Exception table (main) try IllegalState → catch try any → fin* catch any → fin* fin* = copy of finally that rethrows after running withdraw is pushed; balance is 600. Exception #1 created; withdraw's frame popped. Row 1 covers the try and matches the type. Jump to the catch; e refers to #1. javac copied finally onto this path. Nothing pending: normal flow resumes. myphatshala.in
6 Real-world use

try, catch and finally in real systems

The pattern is everywhere a system must stay consistent when something fails halfway.

Watch it with an example: a seat lock that must always be released

A cinema app locks a seat while payment runs. Whether payment succeeds or fails, finally releases the lock, so the seat never stays stuck.

finally runs on success and on failure 6 steps
Animated example lockSeat("F7"); try { pay(amount); } catch (PaymentException e) { show("Payment failed"); } finally { unlockSeat("F7"); } Seat F7 F7 F7 locked free Run 1: payment OK Run 2: bank timeout try → finally exception → catch catch → finally finally runs in both cases (also on return, and even if the catch throws) myphatshala.in

1. Releasing locks

Code that locks a seat during booking, or a shared resource in a multithreaded program, unlocks it in finally. Forgetting to unlock after an exception would block everyone else.

lock()→try { book } finally { unlock() }→Seat free for others

2. Database transactions

Transfer money: debit one account, credit another. If anything throws in between, the catch rolls the transaction back so money is never lost halfway; finally returns the connection to the pool.

Transfer→try { commit } catch { rollback } finally { release }→All or nothing

3. Measuring time and counting requests

Services record how long each request took and whether it failed. The timer is stopped in finally, so slow failures are measured too, not only successes.

start = now→finally { record(now - start) }→Dashboards
retry

4. Retrying flaky calls

Calls to payment gateways, SMS providers or other services sometimes time out. A loop with try/catch retries a few times before giving up, logging each attempt.

Call fails→catch → wait → retry→Success or clear failure

Examples are simplified. Real code usually uses try-with-resources (Lesson 9.6) for connections and files, and framework support for transactions and retries.

7 Run the code

Riya's ATM, with the card always returned

public class Main {
    static int balance = 1000;

    static void withdraw(int amount) {
        if (amount > balance) {
            throw new IllegalStateException("Insufficient balance");
        }
        balance -= amount;
    }

    public static void main(String[] args) {
        int[] requests = {400, 900, 500};
        for (int amount : requests) {
            System.out.println("Request: Rs " + amount);
            try {
                withdraw(amount);
                System.out.println("  Take your cash");
            } catch (IllegalStateException e) {
                System.out.println("  Declined: " + e.getMessage());
            } finally {
                System.out.println("  Card returned");
            }
        }
        System.out.println("Balance: Rs " + balance);
    }
}
Request: Rs 400 Take your cash Card returned Request: Rs 900 Declined: Insufficient balance Card returned Request: Rs 500 Take your cash Card returned Balance: Rs 100
  • throw new IllegalStateException(…)How withdraw reports a problem. You'll write your own throws in Lesson 9.4.
  • System.out.println(" Take your cash");Inside try, after the risky call: it runs only if the withdrawal worked.
  • catch (IllegalStateException e)Runs only for the Rs 900 request. The loop then continues with Rs 500.
  • finally { … "Card returned" }Printed three times: after both successes and after the failure.
8 Line by line

finally runs even after return

Both calls return from inside try or catch, and finally still runs before the method actually returns. Watch the pending return value.

Worked examples: real problems, solved and traced in memory

Three everyday jobs where try, catch and finally each have a clear role. Run each solution, then step through it to see where control jumps when an exception is thrown in a called method, and notice that finally runs on every path.

Example 1Banking · ATM

The ATM always returns your card

Problem statement

An ATM session must hand the card back whether the withdrawal works or not. Write session(amount): it calls withdraw, prints the result or the error, and returns the card in finally. Run one withdrawal that works and one that is larger than the balance.

Input
balance = Rs 1000
withdraw Rs 400, then Rs 900
Expected output
Take your cash: Rs 400
Card returned
Declined: Insufficient balance
Card returned
Balance left: Rs 600

Approach

  1. withdraw throws an IllegalStateException when the balance is too low (writing throw yourself is covered in Lesson 9.4).
  2. session wraps the call in try, handles the problem in catch, and returns the card in finally.
  3. Watch the second call: the exception leaves withdraw, skips the "Take your cash" line, runs the catch, and then the finally.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 2E-commerce · order validation

Different problems, different catch blocks

Problem statement

An order line has a product number and a quantity typed as text. A bad quantity and an unknown product need different messages. Write lineAmount to compute price × quantity, and handle both problems in the caller with two catch blocks.

Input
prices = {250, 40}
lines: (1, "3"), (0, "1O"), (5, "2")
Expected output
Line 1: Rs 120
Line 2: bad quantity
Line 3: unknown product

Approach

  1. lineAmount parses the quantity, then reads prices[product]. Either step can throw.
  2. main loops over the lines with a try per line, so one bad line doesn't stop the rest.
  3. Two catch blocks: NumberFormatException for the quantity, ArrayIndexOutOfBoundsException for the product. Only the matching one runs.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 3Fintech · payments

Retry a flaky payment gateway

Problem statement

A payment gateway times out now and then. Try to charge Rs 1,499 up to three times, stop at the first success, and log the end of every attempt in finally. The gateway is simulated: it fails on the first call only.

Input
amount = Rs 1499, maximum 3 attempts
Expected output
Attempt 1 failed: Gateway timeout
Attempt 1 logged
Attempt 2 logged
Result: TXN502

Approach

  1. charge counts its calls in a static field and throws on the first one to simulate a timeout.
  2. The loop keeps trying while there is no transaction id and attempts remain.
  3. catch handles the timeout so the loop can continue; finally logs every attempt; the loop condition stops after a success.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

9 Quiz and revision

Check yourself

Quick revision

Lesson 9.2 in one screen

  • try = risky code; stops at the first exception. catch = handler for a type. finally = always runs.
  • Catches are checked top to bottom; only the first match runs.
  • Subclass catches must come before superclass catches, or it's a compile error.
  • Multi-catch A | B (Java 7): unrelated types, parameter is implicitly final.
  • finally runs after normal completion, after a catch, when nothing matches, and after return/break/continue.
  • finally is skipped only by System.exit or a JVM crash/kill.
  • Never return from finally: it replaces the pending value and can hide exceptions.

Interview and viva questions

Can we have a try block without a catch?

Yes, if it has a finally block (try-finally) or declares resources (try-with-resources). The exception is not handled there; finally runs and then the exception continues to the caller.

Why must a subclass exception be caught before its superclass?

The JVM picks the first matching catch. If the superclass came first, it would match every subclass exception too, so the subclass catch could never run. Java treats unreachable code as an error, so it refuses to compile.

When does finally not execute?

When the JVM stops before reaching it: System.exit() (or Runtime.halt()), a JVM crash, the process being killed, or the power going off. It also never runs if the try block never finishes, for example an infinite loop.

What happens if both try and finally throw exceptions?

The exception from finally wins and the original one is lost, just like with a return in finally. This is one reason try-with-resources (Lesson 9.6) exists: it keeps the original exception and attaches the closing failure as a suppressed exception.

How is finally implemented in bytecode?

javac copies the finally code onto every normal exit path (after the try, after each catch) and adds an exception-table entry of type “any” whose handler runs another copy of the finally code and then rethrows the exception with athrow. You can see the copies with javap -c.

Practice problems

Practise placing try, catch and finally so the right code runs in every situation. Predict the output before you open each solution. The last three are real business tasks.

P1Easy

Parse with a default value

Write toIntOrDefault(text, def) that returns the number in text, or def when the text is not a valid integer.

Input
"250" with default 0, "25O" with default 0, "" with default -1
Expected output
250
0
-1
Show solution and explanation
public class Main {
    static int toIntOrDefault(String text, int def) {
        try {
            return Integer.parseInt(text);
        } catch (NumberFormatException e) {
            return def;
        }
    }

    public static void main(String[] args) {
        System.out.println(toIntOrDefault("250", 0));
        System.out.println(toIntOrDefault("25O", 0));
        System.out.println(toIntOrDefault("", -1));
    }
}
250 0 -1

How it works: if parseInt succeeds, the return inside try hands back the number. If it throws, that return never happens; control jumps to the catch, which returns the default. Every path returns a value, so the compiler is satisfied.

P2Easy

Trace the three blocks

Print a message from each block (try, catch, finally, after) for two inputs: one that works and one that fails. Predict the order first.

Input
inputs "8" and "eight"
Expected output
try: got 8
finally
after
catch: For input string: "eight"
finally
after
Show solution and explanation
public class Main {
    static void check(String input) {
        try {
            int n = Integer.parseInt(input);
            System.out.println("try: got " + n);
        } catch (NumberFormatException e) {
            System.out.println("catch: " + e.getMessage());
        } finally {
            System.out.println("finally");
        }
        System.out.println("after");
    }

    public static void main(String[] args) {
        check("8");
        check("eight");
    }
}
try: got 8 finally after catch: For input string: "eight" finally after

How it works: with "8" the try finishes, the catch is skipped, finally runs, then the code after. With "eight" the try stops at parseInt, the catch runs, then finally, then the code after. Finally runs in both cases; the catch runs only when its exception type was thrown.

P3Easy

Two catch blocks

Read prices[index] where the index comes as text. Handle a non-number and an index outside the array with two different messages.

Input
prices = {99, 149, 249}; inputs "1", "x", "7"
Expected output
Price: Rs 149
"x" is not a number
No item at 7: Index 7 out of bounds for length 3
Show solution and explanation
public class Main {
    static void show(int[] prices, String indexText) {
        try {
            int i = Integer.parseInt(indexText);
            System.out.println("Price: Rs " + prices[i]);
        } catch (NumberFormatException e) {
            System.out.println("\"" + indexText + "\" is not a number");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("No item at " + indexText + ": " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        int[] prices = {99, 149, 249};
        show(prices, "1");
        show(prices, "x");
        show(prices, "7");
    }
}
Price: Rs 149 "x" is not a number No item at 7: Index 7 out of bounds for length 3

How it works: the JVM checks the catch blocks from top to bottom and runs the first whose type matches the thrown exception. These two types are unrelated (neither is a subclass of the other), so their order doesn't matter here. At most one catch block runs per exception.

P4Medium

One handler for two problems

Use a multi-catch (|) to handle both NumberFormatException and ArithmeticException with the same code, printing which one happened.

Input
pairs: ("100", "4"), ("100", "zero"), ("100", "0")
Expected output
100 / 4 = 25
100 / zero failed: NumberFormatException
100 / 0 failed: ArithmeticException
Show solution and explanation
public class Main {
    static void divide(String a, String b) {
        try {
            int result = Integer.parseInt(a) / Integer.parseInt(b);
            System.out.println(a + " / " + b + " = " + result);
        } catch (NumberFormatException | ArithmeticException e) {
            System.out.println(a + " / " + b + " failed: " + e.getClass().getSimpleName());
        }
    }

    public static void main(String[] args) {
        divide("100", "4");
        divide("100", "zero");
        divide("100", "0");
    }
}
100 / 4 = 25 100 / zero failed: NumberFormatException 100 / 0 failed: ArithmeticException

How it works: a multi-catch (Java 7+) avoids copying the same handler twice. The parameter e has the closest common supertype (here RuntimeException) and is implicitly final. The listed types must not be parent and child of each other; that would be a compile error.

P5Medium

finally counts every attempt

Process a list of codes. Count successes and failures in the try and catch, and count every attempt in finally. Print all three counts.

Input
codes = {"10", "A1", "30", "", "50"}
Expected output
ok=3, failed=2, attempts=5
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        String[] codes = {"10", "A1", "30", "", "50"};
        int ok = 0, failed = 0, attempts = 0;
        for (String c : codes) {
            try {
                Integer.parseInt(c);
                ok++;
            } catch (NumberFormatException e) {
                failed++;
            } finally {
                attempts++;
            }
        }
        System.out.println("ok=" + ok + ", failed=" + failed + ", attempts=" + attempts);
    }
}
ok=3, failed=2, attempts=5

How it works: exactly one of ok++ and failed++ runs per code, and attempts++ runs every time because it is in finally. A quick self-check: ok + failed == attempts. In real code, finally is used for things that must happen whatever the outcome: closing, unlocking, logging time taken.

P6Medium

Who wins: try's return or finally's?

Predict what each method returns. One shows that finally runs after the return value is decided; the other shows the dangerous return-in-finally.

Input
valueKept() and valueReplaced()
Expected output
finally changed x to 99
valueKept returns 10
valueReplaced returns 99
Show solution and explanation
public class Main {
    static int valueKept() {
        int x = 10;
        try {
            return x;
        } finally {
            x = 99;
            System.out.println("finally changed x to " + x);
        }
    }

    @SuppressWarnings("finally")
    static int valueReplaced() {
        try {
            return 10;
        } finally {
            return 99;
        }
    }

    public static void main(String[] args) {
        System.out.println("valueKept returns " + valueKept());
        System.out.println("valueReplaced returns " + valueReplaced());
    }
}
finally changed x to 99 valueKept returns 10 valueReplaced returns 99

How it works: in valueKept, return x saves the value 10 first, then finally runs. Changing the local x afterwards does not change the saved value. In valueReplaced, finally executes its own return, which throws away the pending 10. That is why a return in finally is a bug magnet: it can also discard an exception. javac -Xlint:finally warns about it.

P7Medium

try inside the loop, or the loop inside try?

Add up the same list of entries in two ways: with the try inside the loop, and with the loop inside one try. Print both totals and explain the difference.

Input
entries = {"10", "20", "x", "30"}
Expected output
A: skipped x
B: stopped at a bad entry
A total = 60, B total = 30
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        String[] entries = {"10", "20", "x", "30"};

        int totalA = 0;
        for (String e : entries) {
            try {
                totalA += Integer.parseInt(e);
            } catch (NumberFormatException ex) {
                System.out.println("A: skipped " + e);
            }
        }

        int totalB = 0;
        try {
            for (String e : entries) {
                totalB += Integer.parseInt(e);
            }
        } catch (NumberFormatException ex) {
            System.out.println("B: stopped at a bad entry");
        }

        System.out.println("A total = " + totalA + ", B total = " + totalB);
    }
}
A: skipped x B: stopped at a bad entry A total = 60, B total = 30

How it works: the placement of try decides how much work is abandoned. In A, only the bad entry is skipped and the loop goes on to 30. In B, the exception jumps out of the whole loop to the catch, so 30 is never added. Choose per-item handling when items are independent (orders, rows), and whole-block handling when one failure makes the rest meaningless.

P8Medium

Nested try blocks

An inner try has only a finally; the outer try has the catch. Predict the order of the messages.

Input
inner work divides 10 by 0
Expected output
inner try
inner finally
outer catch: / by zero
outer finally
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        try {
            try {
                System.out.println("inner try");
                int x = 10 / 0;
                System.out.println("inner try end");
            } finally {
                System.out.println("inner finally");
            }
            System.out.println("after inner");
        } catch (ArithmeticException e) {
            System.out.println("outer catch: " + e.getMessage());
        } finally {
            System.out.println("outer finally");
        }
    }
}
inner try inner finally outer catch: / by zero outer finally

How it works: the inner try has no catch, so the exception is not handled there, but its finally still runs on the way out. The exception then continues to the outer try, skipping "after inner", and is caught. Finally blocks run from the innermost outwards, like layers being peeled.

P9Business problem

Bulk order line validation

A wholesale order arrives as lines of product number and quantity text. Price each valid line, reject bad lines with a reason, and print the order total. Use separate catch blocks for each kind of problem.

Input
price list = {120, 75, 300} (product 0..2)
lines = (0,"5"), (2,"two"), (4,"1"), (1,"10")
Expected output
Line 1: Rs 600
Line 2: bad quantity "two"
Line 3: unknown product 4
Line 4: Rs 750
Order total: Rs 1350 (2 lines rejected)
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        int[] price = {120, 75, 300};
        int[] product = {0, 2, 4, 1};
        String[] qty = {"5", "two", "1", "10"};
        int total = 0, rejected = 0;
        for (int i = 0; i < product.length; i++) {
            try {
                int q = Integer.parseInt(qty[i]);
                int amount = price[product[i]] * q;
                total += amount;
                System.out.println("Line " + (i + 1) + ": Rs " + amount);
            } catch (NumberFormatException e) {
                rejected++;
                System.out.println("Line " + (i + 1) + ": bad quantity \"" + qty[i] + "\"");
            } catch (ArrayIndexOutOfBoundsException e) {
                rejected++;
                System.out.println("Line " + (i + 1) + ": unknown product " + product[i]);
            }
        }
        System.out.println("Order total: Rs " + total + " (" + rejected + " lines rejected)");
    }
}
Line 1: Rs 600 Line 2: bad quantity "two" Line 3: unknown product 4 Line 4: Rs 750 Order total: Rs 1350 (2 lines rejected)

How it works:

  1. Each line is handled in its own try, so a bad line never blocks good ones.
  2. total += amount runs only after both risky steps succeed, so a rejected line adds nothing.
  3. Different catch blocks give different, useful reasons. A real system would also validate the product number with an if before using it.
P10Business problem

Payment gateway retries

A payment gateway sometimes times out. Try to charge up to 3 times. Log each attempt in finally, stop as soon as one succeeds, and report the result. (The gateway is simulated: it fails on the first two attempts.)

Input
amount = Rs 1499, max attempts = 3
Expected output
Attempt 1: Gateway timeout
Attempt 1 logged
Attempt 2: Gateway timeout
Attempt 2 logged
Attempt 3: paid, id TXN1003
Attempt 3 logged
Payment complete
Show solution and explanation
public class Main {
    static int calls = 0;

    static String charge(int amount) {
        calls++;
        if (calls < 3) {
            throw new IllegalStateException("Gateway timeout");
        }
        return "TXN" + (1000 + calls);
    }

    public static void main(String[] args) {
        String txn = null;
        for (int attempt = 1; attempt <= 3 && txn == null; attempt++) {
            try {
                txn = charge(1499);
                System.out.println("Attempt " + attempt + ": paid, id " + txn);
            } catch (IllegalStateException e) {
                System.out.println("Attempt " + attempt + ": " + e.getMessage());
            } finally {
                System.out.println("Attempt " + attempt + " logged");
            }
        }
        System.out.println(txn != null ? "Payment complete" : "Payment failed, please retry later");
    }
}
Attempt 1: Gateway timeout Attempt 1 logged Attempt 2: Gateway timeout Attempt 2 logged Attempt 3: paid, id TXN1003 Attempt 3 logged Payment complete

How it works:

  1. The loop condition txn == null stops retrying as soon as a charge succeeds.
  2. Failures are caught per attempt, so the loop can try again; the finally block logs every attempt, success or failure.
  3. charge uses throw to simulate the gateway failing (Lesson 9.4). Real retry code also waits a little longer between attempts and makes sure a payment is never charged twice.
P11Business problem

Settings file with safe defaults

An app reads settings as key=value lines. Numeric settings that are missing or broken must fall back to a default without stopping the app. Print the final settings and how many lines were read.

Input
lines = {"timeout=30", "retries=three", "port=8080", "pool"}
defaults: timeout 10, retries 2, port 80
Expected output
Ignoring "retries=three" (NumberFormatException)
Ignoring "pool" (ArrayIndexOutOfBoundsException)
timeout=30, retries=2, port=8080
4 lines read
Show solution and explanation
public class Main {
    public static void main(String[] args) {
        String[] lines = {"timeout=30", "retries=three", "port=8080", "pool"};
        int timeout = 10, retries = 2, port = 80;
        int read = 0;
        for (String line : lines) {
            try {
                String[] kv = line.split("=");
                int value = Integer.parseInt(kv[1]);
                if (kv[0].equals("timeout")) timeout = value;
                else if (kv[0].equals("retries")) retries = value;
                else if (kv[0].equals("port")) port = value;
            } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
                System.out.println("Ignoring \"" + line + "\" (" + e.getClass().getSimpleName() + ")");
            } finally {
                read++;
            }
        }
        System.out.println("timeout=" + timeout + ", retries=" + retries + ", port=" + port);
        System.out.println(read + " lines read");
    }
}
Ignoring "retries=three" (NumberFormatException) Ignoring "pool" (ArrayIndexOutOfBoundsException) timeout=30, retries=2, port=8080 4 lines read

How it works:

  1. "retries=three" splits fine but parseInt("three") throws NumberFormatException.
  2. "pool" has no =, so split gives a 1-element array and kv[1] throws ArrayIndexOutOfBoundsException.
  3. Both are handled the same way, so a multi-catch keeps the code short. The default value stays in place for any setting that failed, and finally counts every line.

Real-world scenario questions (asked in company interviews)

Most production bugs around exceptions are about where the try is, what is caught and what finally does. Decide your answer first, then open the model answer.

Travel · seat booking1–3 years

Seats stay locked after a failed booking

A booking service locks a seat, charges the customer, then unlocks the seat as the last line of the method. When the payment call throws, customers find that seat shows as “locked” for everyone until the server restarts. What's the bug and the fix?

The interviewer is checking: do you know that clean-up belongs in finally?

Show a strong answer
  • The unlock is the last line, so when payment throws, the rest of the method, including the unlock, is skipped.
  • Fix: lock(); try { charge(); confirm(); } finally { unlock(); }. The lock is taken just before the try, so finally only unlocks what was really locked.
  • Also add a lock timeout as a safety net, and log the payment failure so it can be investigated.

Avoid: copying unlock() into every catch block. A new exception type or an early return would miss it.

Fintech · payments1–3 years

Failed payments reported as successful

A processPayment() method has finally { return "SUCCESS"; } “so the UI always gets a status.” Finance reports that some declined cards were shown to customers as paid. Explain.

The interviewer is checking: do you know that return in finally discards exceptions?

Show a strong answer
  • When the gateway throws a decline exception, finally still runs, and its return replaces the pending exception. The caller receives “SUCCESS” and the exception disappears without any log.
  • Return the real status from the try (success) and the catch (failure with reason); keep finally only for clean-up such as releasing connections or stopping a timer.
  • Enable -Xlint:finally or a static-analysis rule so this pattern fails the build.

Avoid: any return, break or throw inside finally.

E-commerce · code reviewFresher

One catch (Exception e) for everything

A checkout method catches Exception and always shows “Payment failed, try again.” Customers whose address pincode was invalid keep retrying the payment. How would you improve the handling?

The interviewer is checking: can you use multiple catch blocks to give different, useful responses?

Show a strong answer
  • Different problems need different responses: validation errors (bad pincode) should tell the user what to fix; payment declines should say so; a timeout can suggest retrying.
  • Use specific catch blocks, most specific first, and a final general catch only to log unexpected errors with their stack trace.
  • Better still, validate the address before starting payment, so that problem never reaches the payment code.

Avoid: one generic message for every failure. It hides the cause from both users and developers.

Logistics · batch import3+ years

Where to put the try in a nightly import

A nightly job imports 50,000 delivery records. Currently the whole loop is inside one try; one bad record aborts the rest. A teammate proposes a try around every single line inside the loop body. What do you propose?

The interviewer is checking: do you choose the try's scope deliberately?

Show a strong answer
  • One try around the whole loop is too coarse: an error in record 7 skips 49,993 good ones.
  • A try around every line is too fine: the code becomes unreadable and later lines run with half-processed data.
  • Put one try around the processing of one record (the unit of work). On failure, log the record id and reason, add it to an error file, and continue. Use finally for per-record counters or timing.
  • Abort the whole job only for failures that affect every record, such as the database being down, and stop if the error rate passes a threshold.

Avoid: deciding try placement by habit. Match it to the unit of work that can succeed or fail on its own.