1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: Exception best practices = fail fast, fail informatively. Catch specific exceptions (not Exception). Don't swallow (log/rethrow, never empty catch). Use try-with-resources for auto-cleanup. Custom exceptions for domain errors.
- Checked vs Unchecked: Checked = recoverable (IOException), Unchecked = programming bugs (NullPointerException). Don't return null β use Optional. Don't pass null β validate.
- Golden rule: Exceptions for exceptional conditions, not flow control!
Think of fire safety. Exception handling = fire alarm + extinguisher (detect + respond). Specific catches = know fire type (electrical vs kitchen). Don't swallow = don't ignore alarm! try-with-resources = auto-shut-off sprinklers. Custom exceptions = building-specific emergency codes!
2. Conceptual Clarity (The "Simple" Tier)
π‘ The Analogy
- Catch Specific: Doctor diagnosis (specific disease, not "you're sick")
- Don't Swallow: Report car accident (don't pretend it didn't happen)
- try-with-resources: Auto-lock doors (close automatically)
3. Technical Mastery (The "Deep Dive")
java
// ===========================================
// 1. CATCH SPECIFIC EXCEPTIONS
// ===========================================
// β BAD: Catch generic Exception
try {
readFile("data.txt");
parseData(content);
saveToDatabase(data);
} catch (Exception e) { // β Catches everything!
// What went wrong? File? Parse? Database?
}
// β
GOOD: Catch specific exceptions
try {
readFile("data.txt");
parseData(content);
saveToDatabase(data);
} catch (FileNotFoundException e) {
System.err.println("File not found: " + e.getMessage());
} catch (ParseException e) {
System.err.println("Invalid format: " + e.getMessage());
} catch (SQLException e) {
System.err.println("Database error: " + e.getMessage());
}
// ===========================================
// 2. DON'T SWALLOW EXCEPTIONS
// ===========================================
// β BAD: Empty catch (swallows exception)
try {
dangerousOperation();
} catch (Exception e) {
// Ignore β β NEVER DO THIS!
}
// β BAD: Log and continue (might be wrong)
try {
transferMoney(from, to, amount);
} catch (Exception e) {
e.printStackTrace(); // β Money transfer failed silently!
}
// β
GOOD: Log and rethrow
try {
transferMoney(from, to, amount);
} catch (Exception e) {
logger.error("Transfer failed", e);
throw e; // Propagate to caller
}
// β
GOOD: Wrap and throw
try {
processPayment(payment);
} catch (SQLException e) {
throw new PaymentException("Payment processing failed", e);
}
// ===========================================
// 3. TRY-WITH-RESOURCES
// ===========================================
// β BAD: Manual resource management
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close(); // β Verbose, error-prone!
} catch (IOException e) {
e.printStackTrace();
}
}
}
// β
GOOD: try-with-resources (auto-close)
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
// Reader automatically closed!
// Multiple resources
try (Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(SQL);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
// Process
}
}
// All closed automatically in reverse order!
// ===========================================
// 4. CUSTOM EXCEPTIONS
// ===========================================
// β
Create domain-specific exceptions
class InsufficientFundsException extends Exception {
private final double balance;
private final double requested;
InsufficientFundsException(double balance, double requested) {
super(String.format("Insufficient funds: balance=%.2f, requested=%.2f",
balance, requested));
this.balance = balance;
this.requested = requested;
}
public double getBalance() { return balance; }
public double getRequested() { return requested; }
}
// Usage: Provides context
void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(balance, amount);
}
balance -= amount;
}
// ===========================================
// 5. CHECKED VS UNCHECKED
// ===========================================
// β
Checked: For recoverable conditions
public void readFile(String path) throws FileNotFoundException {
// Caller might handle by prompting for different path
}
// β
Unchecked: For programming errors
public void processUser(User user) {
if (user == null) {
throw new IllegalArgumentException("User cannot be null");
}
}
// β BAD: Checked exception for programming error
public void divide(int a, int b) throws DivideByZeroException { // β Should be unchecked!
if (b == 0) throw new DivideByZeroException();
}
// β
GOOD: Unchecked for programming error
public int divide(int a, int b) {
if (b == 0) throw new IllegalArgumentException("Divisor cannot be zero");
return a / b;
}
// ===========================================
// 6. DON'T RETURN NULL
// ===========================================
// β BAD: Return null (forces null checks)
public User findUser(int id) {
User user = database.findById(id);
return user; // β Might be null!
}
// Caller forced to check:
User user = findUser(123);
if (user != null) { // β Easy to forget!
user.getName();
}
// β
GOOD: Use Optional
public Optional<User> findUser(int id) {
return Optional.ofNullable(database.findById(id));
}
// Caller forced to handle:
findUser(123)
.map(User::getName)
.orElse("Unknown");
// ===========================================
// 7. DON'T PASS NULL
// ===========================================
// β BAD: Accept null
public void processOrder(Order order) {
if (order != null) { // β Defensive programming needed everywhere!
// ...
}
}
// β
GOOD: Validate early
public void processOrder(Order order) {
Objects.requireNonNull(order, "Order cannot be null");
// ...
}5. The Comparison & Decision Layer
| Guideline | Bad | Good |
|---|---|---|
| Catching | catch (Exception e) | catch (IOException e) |
| Swallowing | Empty catch | Log + rethrow |
| Resources | Manual close | try-with-resources |
| Null | Return null | Return Optional |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "Checked vs Unchecked exceptionsβwhen to use each?" Answer:
Checked (extends Exception): Recoverable conditions, caller MUST handle
java
// Caller can recover (try different file)
public void readFile(String path) throws FileNotFoundException {
// ...
}Unchecked (extends RuntimeException): Programming errors, shouldn't happen
java
// Programming errorβfix the code!
public void processUser(User user) {
if (user == null) {
throw new IllegalArgumentException("User null"); // Unchecked
}
}Pro-Tips:
- Exception translation (layer boundaries):
java
// Translate low-level exceptions to domain exceptions
public User getUser(int id) throws UserNotFoundException {
try {
return database.find(id);
} catch (SQLException e) {
throw new UserNotFoundException("User not found: " + id, e);
}
}
// Caller doesn't need to know about SQL!- Fail-fast principle:
java
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age);
}
this.age = age;
}
// Catch errors early, not later!