MA-JAVA0 · Module 11
Exceptions, try/catch, and try-with-resources
Topics: Checked vs unchecked · try-with-resources · finally · IllegalArgumentException
Overview
Session 11 — Exceptions, try/catch, and try-with-resources
Failures are normal: disks fill up, users type gibberish, networks drop. Java models many failures as exceptions—objects that carry a message, a stack trace, and sometimes a cause linking to an earlier error. This session teaches you to throw when contracts break, catch only where you can recover or translate the error, and use try-with-resources so files and streams close even when something throws halfway through.
You will distinguish checked exceptions (the compiler nags you until you handle or declare them) from unchecked runtime exceptions (for logic bugs and “this should never happen” states). You will also see why swallowing exceptions empty-handed is worse than crashing loudly.
Time: about 85 minutes.
Concepts
1. Why exceptions exist
Without exceptions, every method would return sentinel codes (-1, null, booleans) and callers would forget to check—errors silently propagate. Exceptions unwind the call stack until a matching catch handles them, or the program terminates with a stack trace you can read.
Two broad families:
- Unchecked (
RuntimeExceptionand subclasses such asIllegalArgumentException,NullPointerException) — the compiler does not force you to declare them on every method. Use them for programmer mistakes or unrecoverable states inside pure logic. - Checked (
IOExceptionand many others) — callers must eithercatchor declarethrowsbecause recovery (retry, ask user for new path) is plausible.
2. Throwing deliberately
public static int requirePositive(int n) {
if (n <= 0) {
throw new IllegalArgumentException("n must be positive, got " + n);
}
return n;
}
Attach messages that include offending values—future you reads logs faster.
3. Catching and finally
try {
riskyWork();
} catch (IllegalStateException ex) {
System.err.println("recoverable: " + ex.getMessage());
} finally {
cleanupAlwaysRuns();
}
finally executes whether try completes normally, throws, or returns—use sparingly when you truly need symmetry (legacy code before try-with-resources).
4. try-with-resources (Java 7+)
Any object implementing java.lang.AutoCloseable can sit in parentheses after try:
try (PrintWriter out = new PrintWriter("notes.txt")) {
out.println("session nine");
} // out.close() called automatically, even if println threw
This pattern prevents leaked file handles when exceptions occur mid-write.
5. Do not catch blindly
Empty catches or broad catch (Exception e) { } hide bugs. Prefer:
- Catch specific types you can meaningfully handle.
- Log or rethrow with context (
throw new IllegalStateException("while parsing row", e);).
6. throws on method signatures
When a method calls checked APIs (new FileInputStream(path)), either wrap in try/catch or declare:
public void load() throws IOException {
// compiler satisfied; callers must now handle or declare too
}
Propagating throws pushes responsibility upward until a layer knows how to recover.
7. Exception types as documentation
Pick exception classes whose names match the failure mode: IllegalStateException when an object is not ready for the operation, IllegalArgumentException when a parameter violates a precondition, UnsupportedOperationException when a method exists for interface symmetry but this implementation refuses it. Readers infer intent from the type even before they read your message string.
8. Chaining causes and when not to catch
When you catch a low-level exception and rethrow a higher-level one, use the two-argument constructor so the original stack is preserved:
} catch (IOException ex) {
throw new IllegalStateException("could not read config", ex);
}
If you cannot log meaningfully, retry, translate, or present a user-facing error, let the exception propagate. A crash with a stack trace during development beats silent corruption. Production services add top-level handlers; beginner programs benefit from loud failure while learning.
Examples
Example A — try-with-resources for writing
import java.io.IOException;
import java.io.PrintWriter;
public class LogSnippet {
public static void writeLine(String path, String line) throws IOException {
try (PrintWriter out = new PrintWriter(path)) {
out.println(line);
}
}
}
If PrintWriter cannot open the file, construction throws—nothing writes partial garbage silently.
Example B — translating to unchecked for API boundaries
public class CsvCell {
public static int parseCount(String cell) {
try {
return Integer.parseInt(cell.trim());
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("not an int: " + cell, ex);
}
}
}
Wrapping preserves the cause (ex) while signalling to callers that this method treats bad input as a contract violation, not as an I/O failure.
Example C — finally for symmetry (before you knew try-with-resources)
Older textbooks open in try, assign null, and close in finally with a null check:
PrintWriter out = null;
try {
out = new PrintWriter("tmp.txt");
out.println("legacy pattern");
} finally {
if (out != null) {
out.close();
}
}
try-with-resources replaces almost all of this boilerplate—memorise the modern form first, recognise the legacy pattern when you read older code.
Example D — narrowing catches
try {
work();
} catch (NumberFormatException ex) {
System.err.println("bad number in input");
} catch (IllegalArgumentException ex) {
System.err.println("bad argument: " + ex.getMessage());
}
Order matters: subclasses (NumberFormatException) must appear before their supertypes (IllegalArgumentException) if both are listed—otherwise the subclass branch is unreachable.
Practice check — exceptions and try-with-resources
Multiple-choice check from the Matheion app lesson. Pick an answer for each question, then submit.
1.Checked vs unchecked exceptions: which must you handle or declare?
2.What does `try-with-resources` guarantee for a resource that implements `AutoCloseable`?
3.When is `IllegalArgumentException` a good choice?
4.Does `finally` run if a `catch` block rethrows?
5.What is a common reason to prefer try-with-resources over a manual `close()` in `finally`?