Lesson Completion
Back to course

Exception Handling Best Practices: Robust Error Management

Beginner
12 minutesβ˜…4.6Java

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

GuidelineBadGood
Catchingcatch (Exception e)catch (IOException e)
SwallowingEmpty catchLog + rethrow
ResourcesManual closetry-with-resources
NullReturn nullReturn 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:

  1. 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!
  1. 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!

Topics Covered

Java Fundamentals

Tags

#java#programming#beginner-friendly

Last Updated

2025-02-01