Exceptions
Java exceptions are objects thrown to signal errors. checked ones (extends Exception) must be declared or caught; unchecked (extends RuntimeException) bubble up freely. Modern Java leans toward unchecked + sealed hierarchies.
Try, catch, throw, try-with-resources
EXAMPLE
// 1) Try / catch
try {
String body = Files.readString(Path.of("config.json"));
parse(body);
} catch (IOException e) {
log.error("failed to read config", e);
throw new RuntimeException(e);
}
// 2) Multiple catches
try {
doWork();
} catch (FileNotFoundException e) {
log.warn("file missing, using defaults");
} catch (IOException e) {
log.error("IO failed", e);
} catch (Exception e) {
log.error("unexpected", e);
throw e;
}
// 3) Multi-catch (Java 7+)
try {
doWork();
} catch (IOException | SQLException e) {
log.error("data error", e);
}
// 4) Try-with-resources — auto-close
try (var reader = Files.newBufferedReader(path);
var writer = Files.newBufferedWriter(out)) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line.toUpperCase() + "\n");
}
}
// Both closed automatically (in reverse order); IOException possible
// Any object implementing AutoCloseable works:
try (Connection conn = DriverManager.getConnection(url, user, pass);
PreparedStatement ps = conn.prepareStatement("SELECT 1")) {
ResultSet rs = ps.executeQuery();
}
// 5) Throw
throw new IllegalArgumentException("age must be >= 0");
throw new IllegalStateException("transaction not started");
throw new MyDomainException("order not found", orderId);
// 6) Throws declaration (checked exceptions)
public String readConfig() throws IOException {
return Files.readString(Path.of("config.json"));
}
public void process() throws IOException, SQLException {
var data = readConfig();
db.insert(data);
}
// 7) Finally — always runs (unless JVM exits)
try {
work();
} finally {
cleanup(); // even if work() throws
}
// Useful for closing resources before try-with-resources existed
// Today: prefer try-with-resources for AutoCloseable
// 8) Custom exceptions
public class OrderNotFoundException extends RuntimeException {
private final String orderId;
public OrderNotFoundException(String orderId) {
super("Order not found: " + orderId);
this.orderId = orderId;
}
public String getOrderId() { return orderId; }
}
throw new OrderNotFoundException("o_42");
// 9) Sealed exception hierarchies (Java 17+)
public sealed class PaymentException extends RuntimeException
permits CardDeclinedException, GatewayDownException, InvalidAmountException {
private final String code;
public PaymentException(String code, String message) {
super(message);
this.code = code;
}
public String getCode() { return code; }
}
public final class CardDeclinedException extends PaymentException {
public CardDeclinedException(String message) { super("CARD_DECLINED", message); }
}
public final class GatewayDownException extends PaymentException {
public GatewayDownException(String message) { super("GATEWAY_DOWN", message); }
}
public final class InvalidAmountException extends PaymentException {
public InvalidAmountException(String message) { super("INVALID_AMOUNT", message); }
}
// Handler — pattern matching on sealed type
String handle(PaymentException e) {
return switch (e) {
case CardDeclinedException c -> "Card declined: " + c.getMessage();
case GatewayDownException g -> "Try again later";
case InvalidAmountException a -> "Bad amount";
};
}
// 10) Re-throwing with context — wrap in a new exception
try {
api.call();
} catch (IOException e) {
throw new ServiceException("API call failed", e); // cause chain preserved
}
// Get the cause later
catch (ServiceException e) {
Throwable cause = e.getCause();
log.error("caused by:", cause);
}
// 11) Stack trace
try {
work();
} catch (Exception e) {
e.printStackTrace(); // bad in prod logs
log.error("work failed", e); // structured
// Manually inspect
for (var frame : e.getStackTrace()) {
System.out.println(frame.getClassName() + "." + frame.getMethodName());
}
}
// Suppress noisy stack traces (e.g. control flow exceptions)
public class SilentException extends RuntimeException {
public SilentException(String message) {
super(message, null, false, false); // disable suppression + stack trace
}
}
// 12) Checked vs unchecked — when to pick
// Checked (extends Exception):
// - Caller MUST handle or declare
// - Compiler enforces
// - Use for RECOVERABLE conditions (file missing, network blip)
// - Java standard library uses this style heavily (IOException, SQLException)
// Unchecked (extends RuntimeException):
// - Caller CAN handle but doesn't have to
// - For programmer errors (null check failure, illegal arg)
// - For framework / app errors that callers usually can't recover from
// - Modern style: use mostly unchecked + Optional for absent values
// 13) Try as expression (Java 14+ enhanced switch is similar, but try isn't an expression yet)
// Workaround: extract to a method
int parsed = parse(input); // method handles try internally
public int parse(String input) {
try { return Integer.parseInt(input); }
catch (NumberFormatException e) { return -1; }
}
// 14) Logging vs throwing
// DON'T:
catch (Exception e) {
log.error("failed", e);
throw e; // double-logged
}
// DO ONE OR THE OTHER:
catch (Exception e) {
throw new RuntimeException("failed", e); // upstream logger handles it
}
// 15) Resource cleanup patterns
// Bad — finally with exception risk
FileInputStream fis = null;
try {
fis = new FileInputStream(path);
// ...
} finally {
if (fis != null) fis.close(); // close throws another IOException — confusing
}
// Good — try-with-resources
try (var fis = new FileInputStream(path)) {
// ...
}
// 16) Performance
// • Throwing is EXPENSIVE (~5-50μs to capture stack trace)
// • Don't use exceptions for control flow
// • Use Optional<T> for absent values, Result<T, E> for fallible (custom)
// • Stack trace capture dominates throw cost; disable via constructor flag if needed
// 17) Try / catch in streams
List<String> lengths = paths.stream()
.map(p -> {
try { return Files.readString(p); }
catch (IOException e) { return ""; }
})
.collect(Collectors.toList());
// Cleaner: helper to wrap checked exceptions
@@FunctionalInterface
interface ThrowingFunction<T, R> {
R apply(T t) throws Exception;
}
static <T, R> Function<T, R> unchecked(ThrowingFunction<T, R> f) {
return t -> {
try { return f.apply(t); }
catch (Exception e) { throw new RuntimeException(e); }
};
}
List<String> contents = paths.stream()
.map(unchecked(Files::readString))
.toList();
// 18) Test exceptions
// JUnit 5
import static org.junit.jupiter.api.Assertions.*;
assertThrows(OrderNotFoundException.class, () -> service.find("missing"));
var e = assertThrows(PaymentException.class, () -> service.charge(card));
assertEquals("CARD_DECLINED", e.getCode());
// 19) Common bugs
// • Empty catch blocks — silently swallow errors
// • Catching Throwable / Exception → catches Error too (OutOfMemoryError, StackOverflowError)
// • Catching + ignoring InterruptedException → must re-set interrupt flag or re-throw
// • Using exceptions for control flow (StopIteration-style)
// • Forgetting to call cause when wrapping — lose stack chain
// • Catching exception, logging, and re-throwing same — double logging
// • Wrapping RuntimeException in another RuntimeException — adds noise without value
// 20) InterruptedException — special case
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // RE-SET the interrupt flag
throw new RuntimeException("interrupted", e);
}
// Lose the flag → upstream loops can't tell
// 21) Best practices
// ✅ Use try-with-resources for any AutoCloseable
// ✅ Prefer unchecked exceptions for app-level errors
// ✅ Build a sealed hierarchy for domain errors
// ✅ Always include cause when wrapping
// ✅ Throw fast — validate input at the boundary
// ✅ Don't catch and re-throw at every layer; let exceptions bubble
// ✅ One catch per type of failure; don't blanket-catch Throwable
// ✅ Log structured, with context (request_id, user_id)
Why it matters
Try-with-resources is the modern resource cleanup pattern; sealed exception hierarchies + pattern-matching switches give you exhaustive handling. Use unchecked exceptions for most app errors and reserve checked for recoverable I/O conditions.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
try {
int n = Integer.parseInt(input);
} catch (NumberFormatException e) {
System.err.println("bad input: " + e.getMessage());
} finally {
cleanup();
}
Try it Yourself »
Discussion
Loading…