Definition
- Catch specific:
catch(SQLException e)notcatch(Exception e). Broad catch hides bugs (NullPointerException caught and logged as "operation failed" instead of crashing loudly). - Never catch(Throwable): it catches
OutOfMemoryError,StackOverflowError, andThreadDeath. The JVM is in an inconsistent state but your code "handles" it and continues. - Never use exceptions for control flow: exception creation captures a stack trace (1 to 5 microseconds), try-catch prevents JIT optimizations, and the code is unreadable.
- Use
Optional,Map.containsKey(),Character.isDigit()instead of catching exceptions for expected conditions.
catch(Exception) is like telling the bank guard "catch ALL problems." Fire alarm? Guard catches it. Armed robbery? Guard catches it. Customer fainted? Caught. Nothing escalates. Every emergency is silently handled. The bank appears to be running fine while it burns down.
Understanding the Concepts
Why should you catch the most specific exception?
Broad catch hides bugs. A NullPointerException gets caught by your generic handler and logged as an "operation failed" instead of crashing loudly so you find the actual bug.
package com.bank.demo;
import java.math.BigDecimal;
import java.util.Objects;
public class CatchSpecificDemo {
static class Trade {
BigDecimal amount;
Trade(BigDecimal amount) { this.amount = amount; }
BigDecimal getAmount() { return amount; }
}
// WRONG: broad catch hides bugs
public static void wrongBroadCatch() {
try {
Trade trade = null; // Bug: should not be null
BigDecimal amount = trade.getAmount(); // NPE!
System.out.println("Amount: " + amount);
} catch (Exception e) {
System.out.println("Wrong: Operation failed - " + e.getClass().getSimpleName());
// NPE caught here. Bug hidden. You think: "service issue"
// Reality: YOUR code has a null bug.
}
}
// CORRECT: specific catch, bug crashes loudly
public static void correctSpecificCatch() {
try {
Trade trade = Objects.requireNonNull(null, "trade required");
System.out.println("Amount: " + trade.getAmount());
} catch (NullPointerException e) {
System.out.println("Correct: Found the bug - " + e.getMessage());
}
}
public static void main(String[] args) {
wrongBroadCatch();
correctSpecificCatch();
}
}
Wrong: Operation failed - NullPointerException
Correct: Found the bug - trade required
Why should you never use exceptions for control flow?
Exception creation captures a stack trace (1 to 5 microseconds). Using exceptions for expected conditions wastes CPU and makes code unreadable.
package com.bank.demo;
import java.math.BigDecimal;
import java.util.Map;
import java.util.Optional;
public class NoExceptionControlFlow {
static Map<String, BigDecimal> accounts = Map.of("SAV2001", new BigDecimal("50000"));
// WRONG: exception for control flow
public static BigDecimal wrongGetBalance(String accountId) {
try {
return accounts.get(accountId); // returns null if not found
} catch (NullPointerException e) {
return BigDecimal.ZERO; // "handling" null as not found
}
// Slow. Hides REAL NPE bugs.
}
// CORRECT: check without exception
public static BigDecimal correctGetBalance(String accountId) {
return Optional.ofNullable(accounts.get(accountId))
.orElse(BigDecimal.ZERO);
// Fast. Readable. No exception overhead.
}
// WRONG: parseInt to check if numeric
public static boolean wrongIsNumeric(String s) {
try {
Integer.parseInt(s);
return true;
} catch (NumberFormatException e) {
return false; // Exception for every non-numeric string!
}
}
// CORRECT: check without exception
public static boolean correctIsNumeric(String s) {
return s != null && !s.isEmpty() && s.chars().allMatch(Character::isDigit);
}
public static void main(String[] args) {
System.out.println("Wrong balance: Rs." + wrongGetBalance("UNKNOWN"));
System.out.println("Correct balance: Rs." + correctGetBalance("UNKNOWN"));
System.out.println("Wrong numeric: " + wrongIsNumeric("abc"));
System.out.println("Correct numeric: " + correctIsNumeric("abc"));
}
}
Wrong balance: Rs.0
Correct balance: Rs.0
Wrong numeric: false
Correct numeric: false
What are common mistakes?
package com.bank.demo;
public class ExceptionBestPracticeMistakes {
// WRONG: catching Throwable catches Errors too
public static void wrongCatchThrowable() {
try {
System.out.println("Processing...");
} catch (Throwable t) {
// Catches OutOfMemoryError, StackOverflowError
// JVM is dying but you "handle" it
System.out.println("Wrong: caught " + t.getClass().getSimpleName());
}
}
// WRONG: empty catch
public static void wrongEmptyCatch() {
try {
throw new RuntimeException("Important error");
} catch (Exception e) {
// Silent. Error disappears.
}
System.out.println("Error was silently swallowed");
}
// CORRECT: specific catch, log, handle
public static void correct() {
try {
throw new IllegalArgumentException("Bad account ID");
} catch (IllegalArgumentException e) {
System.out.println("Specific catch: " + e.getMessage());
// In production: log.error("msg", e); throw;
}
}
public static void main(String[] args) {
wrongCatchThrowable();
wrongEmptyCatch();
correct();
}
}
Processing...
Error was silently swallowed
Specific catch: Bad account ID
What should a senior engineer know?
- Catch(Exception) in frameworks. Spring's @ExceptionHandler catching Exception is acceptable because it is the centralized, intentional catch-all with proper logging. The rule is against broad catches in business logic.
- Catch(Exception) at boundaries. Message listeners (Kafka, SQS) and batch processors are acceptable places for catch(Exception) because uncaught exceptions would kill the consumer thread.
- JIT optimization. try-catch blocks do not prevent JIT optimization. But frequently thrown exceptions force deoptimization paths.
What are common interview follow-up questions?
- When is catch(Exception) acceptable? (In global handlers, message listeners, batch processors.)
- Why not catch(Throwable)? (It catches Errors like OOM and SOE.)
- What is the cost of an exception? (fillInStackTrace: 1 to 5 microseconds per throw.)
- How do you check for a key in a map without exceptions? (containsKey() or Optional.ofNullable(get()).)
Visual Overview
catch Specificity Ladder:
BEST: catch (TradeNotFoundException e) ← most specific
│
GOOD: catch (RuntimeException e) ← broad but safe
│
BAD: catch (Exception e) ← catches all checked + unchecked
│
WORST: catch (Throwable t) ← catches Errors too
Control Flow:
WRONG: try { map.get(key) } catch (NPE) { } ← expensive
RIGHT: Optional.ofNullable(map.get(key)) ← fast, clean
Key Takeaway
Always catch the most specific exception type possible. catch(SQLException e) lets you handle the database error appropriately, while catch(Exception e) silently catches every bug (NullPointerException, ClassCastException) and hides them as "operation failed." catch(Throwable) is even worse because it catches OutOfMemoryError and StackOverflowError, letting the application continue in a corrupted state.
Never use exceptions for ordinary control flow. Checking if a map key exists by catching NullPointerException is roughly 100 times slower than using containsKey(). Validating if a string is numeric by catching NumberFormatException creates an exception object with a full stack trace for every invalid input. Use Optional, containsKey(), and Character.isDigit() instead.
The rule at National Bank of India is: exceptions are for exceptional situations (database down, file missing, network timeout), not for expected business paths (missing map key, invalid format, empty result). If a condition happens regularly, check for it with if/else. If it happens rarely and unexpectedly, let the exception propagate.
Remember one sentence: catch the most specific exception to avoid hiding bugs, and never use exceptions for control flow because they are expensive and unreadable.
---