Java Exception Handling Cheat Sheet
Covers try/catch/finally blocks, try-with-resources, custom checked exceptions, checked versus unchecked exception types, and exception cause chaining in Java.
try/catch/finally
Core exception-handling syntax, including multi-catch ordering.
try { int result = 10 / 0; // throws ArithmeticException} catch (ArithmeticException e) { System.err.println("Math error: " + e.getMessage());} catch (Exception e) { // broader catch, must come after specific ones System.err.println("Unexpected: " + e.getMessage());} finally { System.out.println("Always runs, even after return"); // cleanup}
try-with-resources (Java 7+)
Automatically close AutoCloseable resources, even on exception.
// Resource must implement AutoCloseable; close() is called automaticallytry (BufferedReader reader = new BufferedReader(new FileReader("data.txt")); FileWriter writer = new FileWriter("out.txt")) { String line = reader.readLine(); writer.write(line);} catch (IOException e) { System.err.println("I/O failed: " + e.getMessage());} // both resources closed in reverse order, even on exception
Custom Exceptions
Define a checked exception that carries extra diagnostic data.
public class InsufficientFundsException extends Exception { // checked exception private final double shortfall; public InsufficientFundsException(String message, double shortfall) { super(message); this.shortfall = shortfall; } public double getShortfall() { return shortfall; }}public void withdraw(double amount) throws InsufficientFundsException { if (amount > balance) { throw new InsufficientFundsException("Not enough funds", amount - balance); }}
Checked vs. Unchecked Exceptions
The distinction that determines whether the compiler forces handling.
- Checked exceptions- Extend Exception (not RuntimeException); must be declared with throws or caught, e.g. IOException
- Unchecked exceptions- Extend RuntimeException; not required to be declared or caught, e.g. NullPointerException
- Error- Represents serious JVM-level problems (OutOfMemoryError); not meant to be caught
- throws clause- Declares that a method may propagate a checked exception to its caller
- throw- Statement that actually raises an exception instance
- Multi-catch- catch (IOException | SQLException e) handles multiple types in one block (Java 7+)
- Exception chaining- new CustomException("msg", cause) preserves the original exception via getCause()
Exception Chaining
Wrap a lower-level exception while preserving its original cause.
try { parseConfig();} catch (ParseException e) { throw new RuntimeException("Failed to start application", e); // wraps original cause}// Later:catch (RuntimeException e) { System.err.println(e.getMessage()); System.err.println("Root cause: " + e.getCause());}
Suppressed Exceptions in try-with-resources
When both the try body and close() throw, the close() exception is suppressed, not lost.
class NoisyResource implements AutoCloseable { public void work() { throw new RuntimeException("work failed"); } @Override public void close() { throw new IllegalStateException("close failed"); }}try (NoisyResource r = new NoisyResource()) { r.work();} catch (RuntimeException e) { System.out.println("Primary: " + e.getMessage()); // "work failed" for (Throwable suppressed : e.getSuppressed()) { System.out.println("Suppressed: " + suppressed.getMessage()); // "close failed" }}
Designing an Exception Hierarchy
Layer domain exceptions under a common base so callers can catch broadly or narrowly.
public abstract class PaymentException extends RuntimeException { protected PaymentException(String message, Throwable cause) { super(message, cause); }}public class CardDeclinedException extends PaymentException { private final String declineCode; public CardDeclinedException(String declineCode, Throwable cause) { super("Card declined: " + declineCode, cause); this.declineCode = declineCode; } public String getDeclineCode() { return declineCode; }}public class GatewayTimeoutException extends PaymentException { public GatewayTimeoutException(Throwable cause) { super("Gateway timed out", cause); }}// Callers can catch the specific subtype or the shared PaymentException basetry { charge(order);} catch (CardDeclinedException e) { notifyUser(e.getDeclineCode());} catch (PaymentException e) { retryLater(e);}
Fixing Common Exception Antipatterns
Two classic mistakes that erase diagnostic information, and the fix for each.
// ANTIPATTERN 1: catching Exception/Throwable hides bugs (including NPEs you didn't expect)try { process();} catch (Exception e) { /* too broad - catches RuntimeExceptions you never intended to handle */ }// ANTIPATTERN 2: losing the original cause when rethrowingtry { process();} catch (SQLException e) { throw new RuntimeException("DB failure"); // BAD: cause is lost}// FIX: catch specific types, always chain the causetry { process();} catch (SQLException e) { throw new RuntimeException("DB failure: " + e.getMessage(), e); // GOOD: cause preserved}
Programmatic Stack Trace Inspection
Walk or filter the stack trace instead of just printing it, useful for structured logging.
try { riskyOperation();} catch (Exception e) { StackTraceElement[] trace = e.getStackTrace(); StackTraceElement origin = trace[0]; // where the exception was thrown logger.error("{} at {}.{}({}:{})", e.getClass().getSimpleName(), origin.getClassName(), origin.getMethodName(), origin.getFileName(), origin.getLineNumber()); // Java 9+ StackWalker avoids eagerly capturing the full trace StackWalker.getInstance().walk(frames -> frames.filter(f -> f.getClassName().startsWith("com.myapp")) .findFirst());}
Advanced Exception-Handling Notes
Nuances that matter once exception handling touches performance or API design.
- Exception construction cost- Filling in a stack trace is the expensive part of throwing; override fillInStackTrace() to return this (no-op) for high-frequency control-flow exceptions
- addSuppressed()- Manually attach a secondary failure to a primary exception outside try-with-resources via e.addSuppressed(other)
- Checked exceptions in lambdas- Functional interfaces like Function<T,R> can't declare checked exceptions; wrap the checked call in a try/catch that rethrows unchecked, or define a custom throwing functional interface
- finally overriding a return- A return/throw inside finally silently discards the try block's return value or exception - avoid control-flow statements in finally
- Exception vs Result-style error handling- For expected, frequent failure paths (e.g. validation), consider returning Optional/a Result type instead of throwing, since exceptions imply exceptional (rare) conditions
- getLocalizedMessage()- Override this (instead of getMessage()) when messages need locale-specific text; defaults to getMessage() otherwise
Never catch an exception and swallow it silently (empty catch block) - at minimum log it with the stack trace (e.printStackTrace() or a logging framework), since a silent catch turns a debuggable failure into a mysterious one.