Java exceptions
Checked vs unchecked exceptions in Java
The compiler enforces a catch-or-specify contract on checked exceptions: code that can throw one must sit inside a try that catches it, or the enclosing method must declare it with throws. Do neither and the code will not compile. The split follows the class hierarchy. A checked exception is any subclass of Exception other than RuntimeException and its subclasses, so IOException, SQLException, and InterruptedException are checked. Unchecked means RuntimeException and its subclasses (NullPointerException, IllegalArgumentException) plus Error and its subclasses, all exempt from the compile-time check.
One catch: Exception and Throwable themselves are checked, so throws Exception or catch (Exception e) still forces callers to catch-or-specify. Only the RuntimeException and Error subtrees are exempt.
What catch(Exception) catches and what it misses
Throwable splits into Error and Exception, and Exception contains RuntimeException. So catch (Exception e) catches every application exception, checked or runtime, but does not reach Error.
Wrong "catch (Exception e) catches everything, including OutOfMemoryError." Error is a sibling of Exception, not a subclass, so an OutOfMemoryError or StackOverflowError sails straight past catch (Exception e). Only catch (Throwable t) reaches errors, and that is the pattern to avoid: errors signal JVM or environment trouble the application cannot recover from, so swallowing them hides fatal state. Catch the narrowest type you can act on.
try-with-resources closes in reverse order
Any resource that implements AutoCloseable can go in the header, and it is closed automatically when the block exits, normally or by an exception, in the reverse order of acquisition.
class R implements AutoCloseable {
final String id;
R(String id) { this.id = id; System.out.println("open " + id); }
public void close() { System.out.println("close " + id); }
}
try (R a = new R("a"); R b = new R("b")) {
System.out.println("body");
}
// open a
// open b
// body
// close b // last opened, first closed
// close a
If the body throws and a close() also throws, the body exception wins and the close() one is attached to it as a suppressed exception. It is preserved, though you have to call getSuppressed() to see it.
finally, and rethrowing with the cause
A finally block runs on every normal or abrupt exit from try or catch, including after a return or a thrown exception. It skips only if the JVM stops first: System.exit, a hard crash, or the thread being killed. Keep return and throw out of finally, for reasons covered in the senior notes.
When you catch a low-level exception and rethrow your own, pass the original as the cause so its stack trace survives:
catch (IOException e) {
throw new IllegalStateException("load failed for " + path, e); // e is the cause
}
Throwing an IllegalStateException with only a message drops that cause and makes the failure hard to trace back to its origin.
Suppressed exceptions in try-with-resources
When the try body throws and a resource's close() also throws, the body exception is the primary one and propagates; each close() exception is attached to it through addSuppressed and read back with getSuppressed(), which returns an empty array when there is none.
class Res implements AutoCloseable {
public void close() { throw new RuntimeException("close-fail"); }
void work() { throw new RuntimeException("body-fail"); }
}
try (Res r = new Res()) { r.work(); }
catch (Exception e) {
System.out.println("Caught: " + e.getMessage());
for (Throwable s : e.getSuppressed())
System.out.println("Suppressed: " + s.getMessage());
}
// Caught: body-fail
// Suppressed: close-fail
This repairs the pre-7 hand-written pattern where close() ran in a finally and its exception replaced the body's, hiding the real failure. printStackTrace renders suppressed frames under a Suppressed: heading. addSuppressed rejects null and rejects a throwable suppressing itself. A subclass can pass enableSuppression=false to the four-argument Throwable constructor to switch suppression off for cheap control-flow exceptions.
Why return or throw in finally swallows exceptions
The rule is about abrupt completion. If finally completes abruptly for some reason S (a return or a throw), the whole try statement completes for reason S, and the pending reason R from try or catch is discarded.
static int f() {
try { throw new IllegalStateException("boom"); }
finally { return 42; } // discards the exception
}
// f() returns 42; the IllegalStateException never propagates
Wrong "A return in finally is a tidy way to set the result." It silently eats whatever try was throwing or returning. Right keep control flow out of finally and use it only for cleanup. A related trap: the return expression is evaluated before finally runs, so mutating a local variable there does not change the value already captured.
static int g() {
int x = 1;
try { return x; } // 1 is captured here
finally { x = 99; } // does not change the returned value
}
// g() returns 1
The override contract for checked exceptions
An overriding method may not declare a checked exception broader than, or new to, what the overridden method declares. It may declare the same type, a subtype, fewer, or none; a broader or brand-new checked exception will not compile. Unchecked exceptions are unrestricted, so an override can throw any RuntimeException regardless of the parent signature. The throws clause is part of the caller's contract: a caller holding a base-type reference must not be surprised by a checked exception the base type never advertised.
Chaining, custom exceptions, and initCause pitfalls
Idiomatic wrapping uses the cause-taking constructor, new AppException("msg", e), which keeps the original and renders it under a Caused by: section in the stack trace. initCause exists for legacy classes that lack such a constructor. It may be called at most once: calling it after the cause was already set (including through a cause constructor) throws IllegalStateException, and initCause(this) throws IllegalArgumentException. A custom exception should subclass Exception for a recoverable, contract-level failure or RuntimeException for a bug, supply the four standard constructors delegating to super, and declare a serialVersionUID because Throwable is Serializable. The checked-in-a-lambda problem from the sample question is one reason stream-heavy code often leans toward unchecked custom exceptions.
beta
The interviewer part is in the works.
The diagnostic, personal maps, and AI mock interviews are being finished right now. The notes stay free either way. Leave an email and you'll get the first-cohort invite, plus a month of Pro when it opens.