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
- Catching
ThrowableorError— masks serious JVM problems. - Logging and rethrowing same level repeatedly — log once at boundary.
- Empty catch — hides bugs.
- finally return — overrides try/catch return value.
- Ignoring checked exceptions with bare
throws Exceptionon public API.
Practice checkpoint
-
Read lines from file with try-with-resources skeleton.
- Answer:
try (var lines = Files.lines(path)) { ... }
- Answer:
-
Checked vs unchecked—who enforces?
- Answer: Compiler enforces checked; unchecked optional at compile time.
-
When
IllegalStateExceptionvsIllegalArgumentException?- Answer: Bad argument vs object not ready for operation.
-
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.requireNonNulland guards with meaningful messages.