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.
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.
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.
So try is the risky work, catch is the plan B, and finally is the “no matter what” part?
Exactly. And when you have several plan Bs, the order of the catch blocks matters. Let's see why.
What are try, catch and finally?
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).
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
e, that receives the exception object.catch (A | B e). Java 7+.The rules at a glance
| Valid shapes | try-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 catches | Subclass before superclass. A superclass first makes the later one unreachable: compile error. |
| Multi-catch | catch (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 run | if 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.
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.
Recovertry-catch-finally
Handle the problem and also clean up on every path: return the card, unlock the record, stop the timer.
Recover + clean uptry-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 onWhere 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());
}
}
}
}| Situation | Rest of try | catch | finally | Code after the try statement |
|---|---|---|---|---|
No exception ("4") | Runs | Skipped | Runs | Runs |
Exception caught ("abc") | Skipped | Matching one runs | Runs | Runs |
No matching catch ("0") | Skipped | None runs | Runs | Skipped: 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”.
Writing a safe block, start to finish
Wrap only the risky work in try
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.try { withdraw(amount); System.out.println("Take your cash"); }Add catch blocks, most specific first
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.catch (NumberFormatException e) { … } // subclass first catch (IllegalArgumentException e) { … } // its parent afterSame handling? Use multi-catch
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) andcatch (NumberFormatException | ArrayIndexOutOfBoundsException e) { System.out.println("Bad line: " + e); }ecannot be reassigned.Put must-happen work in finally
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 withfinally { System.out.println("Card returned"); }javap -c.Know the exceptions to “always”
try { System.exit(0); // JVM shuts down here } finally { System.out.println("never printed"); }System.exitstarts 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.
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.
- try: the request for Rs 900 starts, and
withdraw(900)is called inside the try block. - Throw: the balance is only Rs 600, so
withdrawthrows anIllegalStateException. Nothing prints yet. - Skip: “Take your cash” is never printed. The try block stops at the line that failed.
- catch: the catch for
IllegalStateExceptionmatches and prints the exception's message. - finally: on this path too, the card is returned.
- Continue: the exception was handled, so the loop moves on to the next request.
- Control is inside the try block. Calling
withdrawpushes its frame on the stack. withdrawcreates exception object #1 and throws it. It has no handler, so its frame is popped and the exception arrives back in the try block of the caller.- The JVM scans the caller's exception table top to bottom. Row 1 covers the try block's range and its type matches, so control jumps to the catch code. The rest of the try is skipped.
- The catch parameter
enow refers to #1. Only this catch runs. - After the catch, the copy of the finally code that javac placed on this path runs. If no catch had matched, the hidden “any” row would run another copy and then rethrow.
- The exception has been handled, so nothing is pending. #1 is no longer referenced and the next statement runs as usual.
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.
- Lock: seat F7 is locked so nobody else can book it during payment.
- Run 1, try:
pay(amount)succeeds. No exception, so the catch block is skipped. - finally: runs anyway and unlocks F7 (the booking is saved elsewhere). Code in finally runs whether or not an exception happened.
- Run 2: another customer's payment times out inside
pay()and throwsPaymentException. - catch: the rest of the try is skipped; the catch shows “Payment failed”.
- finally again: the seat is released, so someone else can book it. Without finally, a failed payment would leave seats locked forever. Locks, files and database connections are released this way (or with try-with-resources, Lesson 9.6).
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.
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.
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.
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.
Examples are simplified. Real code usually uses try-with-resources (Lesson 9.6) for connections and files, and framework support for transactions and retries.
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);
}
}throw new IllegalStateException(…)Howwithdrawreports 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.
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.
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.
balance = Rs 1000 withdraw Rs 400, then Rs 900
Take your cash: Rs 400 Card returned Declined: Insufficient balance Card returned Balance left: Rs 600
Approach
withdrawthrows anIllegalStateExceptionwhen the balance is too low (writingthrowyourself is covered in Lesson 9.4).sessionwraps the call in try, handles the problem in catch, and returns the card in finally.- 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.
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.
prices = {250, 40}
lines: (1, "3"), (0, "1O"), (5, "2")Line 1: Rs 120 Line 2: bad quantity Line 3: unknown product
Approach
lineAmountparses the quantity, then readsprices[product]. Either step can throw.mainloops over the lines with a try per line, so one bad line doesn't stop the rest.- Two catch blocks:
NumberFormatExceptionfor the quantity,ArrayIndexOutOfBoundsExceptionfor 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.
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.
amount = Rs 1499, maximum 3 attempts
Attempt 1 failed: Gateway timeout Attempt 1 logged Attempt 2 logged Result: TXN502
Approach
chargecounts its calls in a static field and throws on the first one to simulate a timeout.- The loop keeps trying while there is no transaction id and attempts remain.
- 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.
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.exitor 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.
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.
"250" with default 0, "25O" with default 0, "" with default -1
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));
}
}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.
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.
inputs "8" and "eight"
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");
}
}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.
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.
prices = {99, 149, 249}; inputs "1", "x", "7"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");
}
}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.
One handler for two problems
Use a multi-catch (|) to handle both NumberFormatException and ArithmeticException with the same code, printing which one happened.
pairs: ("100", "4"), ("100", "zero"), ("100", "0")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");
}
}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.
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.
codes = {"10", "A1", "30", "", "50"}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);
}
}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.
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.
valueKept() and valueReplaced()
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());
}
}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.
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.
entries = {"10", "20", "x", "30"}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);
}
}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.
Nested try blocks
An inner try has only a finally; the outer try has the catch. Predict the order of the messages.
inner work divides 10 by 0
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");
}
}
}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.
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.
price list = {120, 75, 300} (product 0..2)
lines = (0,"5"), (2,"two"), (4,"1"), (1,"10")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)");
}
}How it works:
- Each line is handled in its own try, so a bad line never blocks good ones.
total += amountruns only after both risky steps succeed, so a rejected line adds nothing.- Different catch blocks give different, useful reasons. A real system would also validate the product number with an
ifbefore using it.
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.)
amount = Rs 1499, max attempts = 3
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");
}
}How it works:
- The loop condition
txn == nullstops retrying as soon as a charge succeeds. - Failures are caught per attempt, so the loop can try again; the finally block logs every attempt, success or failure.
chargeusesthrowto 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.
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.
lines = {"timeout=30", "retries=three", "port=8080", "pool"}
defaults: timeout 10, retries 2, port 80Ignoring "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");
}
}How it works:
"retries=three"splits fine butparseInt("three")throwsNumberFormatException."pool"has no=, sosplitgives a 1-element array andkv[1]throwsArrayIndexOutOfBoundsException.- 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.
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.
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
returnreplaces 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:finallyor a static-analysis rule so this pattern fails the build.
Avoid: any return, break or throw inside finally.
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.
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.