Exceptions and Error Handling

Learning goals

Throw and catch exceptions deliberately, distinguish checked vs unchecked, use try-with-resources for cleanup, and design error handling that preserves debuggability without abusing exceptions for control flow.


Exception hierarchy

Throwable
├── Error (OutOfMemoryError, StackOverflowError) — don't catch routinely
└── Exception
    ├── RuntimeException (unchecked)
    │     NullPointerException, IllegalArgumentException, ...
    └── IOException, SQLException, ... (checked)

Checked — compiler requires handle or declare (throws).

Unchecked (RuntimeException) — programming bugs or unrecoverable client errors; no declare requirement.


try / catch / finally

try {
    processFile(path);
} catch (IOException e) {
    logger.error("Failed: " + path, e);
    throw e;
} finally {
    // runs always (avoid heavy logic; suppresses primary exception if throws)
}

Catch specific types before general:

catch (FileNotFoundException e) { ... }
catch (IOException e) { ... }

try-with-resources (JDK 7+)

Auto-closes AutoCloseable resources:

try (var in = Files.newInputStream(path);
     var reader = new BufferedReader(new InputStreamReader(in))) {
    return reader.readLine();
}
// closed in reverse order, even if exception thrown

Prefer over manual finally { close(); }.


Throwing exceptions

public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("age must be >= 0: " + age);
    }
    this.age = age;
}

Use IllegalArgumentException for bad parameters, IllegalStateException for wrong object state.


throws clause

public String readFirstLine(Path path) throws IOException {
    try (var lines = Files.lines(path)) {
        return lines.findFirst().orElse("");
    }
}

Caller must handle or declare checked exceptions. Don’t blanket throws Exception.


Custom exceptions

public class InsufficientFundsException extends Exception {
    private final long shortfall;

    public InsufficientFundsException(long shortfall) {
        super("Short by " + shortfall);
        this.shortfall = shortfall;
    }

    public long getShortfall() {
        return shortfall;
    }
}

Extend Exception for checked (force handling) or RuntimeException for optional handling.


Multi-catch and rethrow

try {
    risky();
} catch (IOException | SQLException e) {
    throw new DataAccessException("failed", e);
}

Suppressed exceptions — try-with-resources adds close failures as suppressed on primary exception; inspect with getSuppressed().


Best practices

  • Fail fast with clear messages; include cause: new X("msg", cause).
  • Don’t swallow:
catch (Exception e) { }  // anti-pattern
  • Never use exceptions for normal control flow — expensive and obscures logic.
  • Validate inputs early; reserve exceptions for exceptional conditions.

Common mistakes

  1. Catching Throwable or Error — masks serious JVM problems.
  2. Logging and rethrowing same level repeatedly — log once at boundary.
  3. Empty catch — hides bugs.
  4. finally return — overrides try/catch return value.
  5. Ignoring checked exceptions with bare throws Exception on public API.

Practice checkpoint

  1. Read lines from file with try-with-resources skeleton.

    • Answer: try (var lines = Files.lines(path)) { ... }
  2. Checked vs unchecked—who enforces?

    • Answer: Compiler enforces checked; unchecked optional at compile time.
  3. When IllegalStateException vs IllegalArgumentException?

    • Answer: Bad argument vs object not ready for operation.
  4. What closes first in nested try-with-resources?

    • Answer: Resources closed in reverse declaration order.

Interview angles

  • “Checked exceptions controversy?” — Verbose; many libraries wrap in unchecked; still used for recoverable I/O in enterprise code.
  • “try-with-resources requirement?” — Resource must implement AutoCloseable.
  • “NPE vs explicit validation?” — Prefer Objects.requireNonNull and guards with meaningful messages.

Next: /java/learn/core/generics/