Lesson Completion
Back to course

Exception Best Practices: Writing Robust Error Handling

Intermediate
15 minutesβ˜…4.5Java

1. The Hook (The "Byte-Sized" Intro)

In a Nutshell: Good exception handling follows key principles: catch specific exceptions, never swallow errors, clean up resources, provide meaningful messages, document exceptions, and fail fast. Bad practices include empty catch blocks, catching generic Exception, and using exceptions for control flow.

Think of airline safety protocols. They don't say "handle any problem somehow." They have specific procedures for engine failure, decompression, fireβ€”and they ALWAYS report incidents. Same with exceptions!


2. Conceptual Clarity (The "Simple" Tier)

πŸ’‘ The Analogy: The Emergency Response

  • βœ… Good: Specific training for each emergency type, always report, clear protocols
  • ❌ Bad: "Deal with any problem however," ignore incidents, no documentation

3. Technical Mastery (The "Deep Dive")

The Golden Rules

  1. Catch Specific Exceptions (not generic)
  2. Never Swallow Exceptions (empty catch = bug hiding)
  3. Clean Up Resources (try-with-resources or finally)
  4. Provide Context (meaningful messages)
  5. Document Exceptions (@throws in Javadoc)
  6. Fail Fast (validate early)
  7. Don't Use for Control Flow (exceptions are expensive)

4. Interactive & Applied Code

java
// ❌ BAD PRACTICES class BadPractices { void badExample1() { try { // risky code } catch (Exception e) { // ❌ Empty catch (swallowing exception!) } } void badExample2() { try { // code } catch (Exception e) { // ❌ Too generic! e.printStackTrace(); // ❌ Just printing (not handling!) } } void badExample3() { // ❌ Using exceptions for control flow try { while (true) { array[index++]; } } catch (ArrayIndexOutOfBoundsException e) { // Reached end } } } // βœ… GOOD PRACTICES class GoodPractices { /** * Processes user data * @param userData Raw user input * @throws IllegalArgumentException if userData is null or empty * @throws DataProcessingException if processing fails */ void goodExample1(String userData) { // βœ… Fail fast (validate early) if (userData == null || userData.isEmpty()) { throw new IllegalArgumentException("User data cannot be null or empty"); } processData(userData); } void goodExample2() { // βœ… Catch specific exceptions try { readFile("data.txt"); } catch (java.io.FileNotFoundException e) { // βœ… Specific handling + logging logger.error("File not found: {}", e.getMessage()); // βœ… Provide fallback useDefaultData(); } catch (java.io.IOException e) { // βœ… Different handling for different errors logger.error("IO error", e); throw new DataAccessException("Failed to read data", e); } } void goodExample3() { // βœ… Try-with-resources (automatic cleanup) try (java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader("data.txt"))) { String line = reader.readLine(); processLine(line); } catch (java.io.IOException e) { // βœ… Meaningful error message with context throw new DataLoadException( "Failed to load data from file: data.txt", e ); } } // Placeholder methods void processData(String data) {} void readFile(String name) throws java.io.IOException {} void useDefaultData() {} void processLine(String line) {} java.util.logging.Logger logger = java.util.logging.Logger.getLogger("App"); } // βœ… Custom exception with context class DataProcessingException extends RuntimeException { private final String dataId; public DataProcessingException(String message, String dataId, Throwable cause) { super(message + " (Data ID: " + dataId + ")", cause); this.dataId = dataId; } public String getDataId() { return dataId; } } class DataAccessException extends RuntimeException { public DataAccessException(String message, Throwable cause) { super(message, cause); } } class DataLoadException extends RuntimeException { public DataLoadException(String message, Throwable cause) { super(message, cause); } }

5. The Comparison & Decision Layer

Best Practices Checklist

Practice❌ Badβœ… Good
Catchcatch (Exception e)catch (FileNotFoundException e)
Empty catchcatch (E e) {}catch (E e) { log(e); }
ResourcesManual close in finallyTry-with-resources
Messagesthrow new Exception()throw new Exception("Clear message")
DocumentationNo @throws@throws IOException if...
Control flowUse exceptionsUse normal if/loops

6. The "Interview Corner" (The Edge)

The "Killer" Interview Question: "Why shouldn't you catch generic Exception?" Answer: Catching Exception catches everything (even RuntimeExceptions you didn't expect). This:

  1. Hides bugs (catches NullPointerException you should fix)
  2. Prevents specific handling (can't distinguish errors)
  3. Catches too much (maybe even Errors!)

Always catch specific exceptions you know how to handle.

Pro-Tips:

  1. Log, then rethrow at layer boundaries:
java
catch (IOException e) { logger.error("Data access failed", e); throw new DataAccessException("Cannot load user", e); }
  1. Use exception chaining to preserve root cause:
java
throw new BusinessException("Order failed", originalException);
  1. Validate with Objects.requireNonNull() (Java 7+):
java
this.name = Objects.requireNonNull(name, "Name required");

Topics Covered

Java FundamentalsError Handling

Tags

#java#exceptions#try-catch#error-handling#robust-code

Last Updated

2025-02-01