Overview

4 Exceptions: Try, Catch, Log

This chapter explains how to think about and work with exceptions so your Java applications fail predictably and your logs stay useful. Exceptions are “exceptional conditions” that interrupt normal control flow and should be defined by clear preconditions and postconditions. If you ignore or mishandle them, programs crash or become unstable—and if you handle them without logging, you lose critical clues. Because exceptions carry messages and stack traces, the goal is to understand when and how they arise so you can try, catch, and log them in a way that preserves context without creating noise.

It distinguishes checked from unchecked exceptions: checked ones must be handled or declared, while unchecked (RuntimeException and its subclasses) can propagate without compiler pressure. Use try/catch to recover and log with meaningful messages, proper severity (e.g., ERROR/FATAL), and the exception object to capture the stack trace; never leave catch blocks empty. Catch the most specific exceptions first, then broader ones, as order matters. Throw defensively to fail fast (IllegalArgumentException for invalid values, NullPointerException for unexpected nulls). Avoid two antipatterns that bloat or mislead logs: logging input before validation (risking log injection and huge entries) and logging an exception only to rethrow it (duplicating messages). When wrapping exceptions, retain the cause (exception chaining) and let the code that ultimately handles the failure do the logging—who handles, logs.

For resource cleanup, prefer try-with-resources over finally to ensure deterministic closing and to prevent misleading “closed” messages; if you must use finally, keep logging minimal and accurate. Modern loggers walk cause chains automatically, so you don’t need to log each layer. Errors (like StackOverflowError and OutOfMemoryError) signal unrecoverable JVM states—don’t throw, catch, or rely on logging them; fix the root cause instead. In development, step through code with a debugger; in production, clear, contextual logs and stack traces are your primary evidence. The chapter’s practices keep exception handling robust and your logs concise, truthful, and actionable.

Normal program flow
CH04 F01 exceptions flow
Throwing an exception
CH04 F02 exception is thrown
Hierarchy of common Java exceptions
CH04 F03 exception hierarchy
Catching an exception
CH04 F04 exception catched
Catching a military aircraft exception
CH04 F05 catch military aircraft
Catching multiple exceptions
CH04 F06 catch multiple exceptions
Ignoring an exception
CH04 F07 uncatched exception
Chaining multiple exceptions
CH04 F09 exception chaining
Complete exception and error hierarchy
CH04 F08 exception hierarchy complete with errors

Summary

In this chapter…​

  • Checked and unchecked exceptions serve different purposes
  • Handling, declaring, or ignoring exceptions are the three options we have
  • Avoid the "catch-log-throw" and "log-everything" antipatterns
  • Wrapping exceptions is no problem for logging
  • try-with-resources often beats finally

FAQ

What is an exception in Java, and what makes a situation “exceptional”?Exceptions represent unexpected, non-normal situations that disrupt the regular flow of a program. A condition is “exceptional” when it violates a method’s preconditions or prevents its postconditions from being met. Example: a heading calculation receiving a value outside 0–359 violates a precondition and should throw an exception.
What’s the difference between checked and unchecked exceptions?Checked exceptions extend Exception (but not RuntimeException) and must be handled or declared. Unchecked exceptions extend RuntimeException; they don’t need to be declared or caught, though you may still do so. You can tell which is which by inspecting the inheritance hierarchy.
What does “handle or declare” mean for checked exceptions?If a method calls code that can throw a checked exception (e.g., SQLException), it must either handle it with try/catch or declare it with throws in the method signature. Callers then face the same choice until some code actually handles the problem.
How should I log exceptions correctly inside a catch block?Log with a meaningful message, use an appropriate level (typically ERROR or FATAL for critical issues), and pass the exception object so the stack trace and causes are recorded. Avoid vague messages like “Catch”; include relevant context (what operation failed, key identifiers).
How do I avoid catching the wrong exception when types form a hierarchy?Catch specific exceptions first and more general ones afterward. A catch for a superclass also matches all its subclasses, which can hide severity differences and lead to generic messages. Use multiple catch blocks to handle specific cases differently and log at suitable levels.
How should I treat unchecked exceptions and errors?Unchecked exceptions (e.g., NullPointerException, ArrayIndexOutOfBoundsException) often indicate programming errors. Prefer preventing them with unit tests instead of surrounding everything with try/catch. Errors (e.g., OutOfMemoryError, StackOverflowError) signal an unrecoverable JVM state—don’t throw, catch, or log them; the JVM may terminate before logging occurs.
When should I throw IllegalArgumentException vs NullPointerException for invalid input?Use IllegalArgumentException for invalid but non-null arguments (e.g., out-of-range values). Use NullPointerException when a parameter that must not be null is null—this follows Java convention (as recommended in Effective Java). Fail fast (defensive programming), and don’t log at the throw site; let the handler log.
Which logging antipatterns should I avoid?- Log-everything-before-validation: Logging raw inputs before validating them risks huge log spam and log injection. Validate first; if you throw, let the handler log.
- Log-and-throw: Catching, logging, and then rethrowing causes duplicate log entries and noisy, misleading traces. Simple rule: the code that handles the problem should be the code that logs it.
When should I use finally vs try-with-resources for cleanup and logging?finally always runs (except on System.exit()) and is traditionally used for cleanup, but it’s easy to produce misleading logs (e.g., “Connection closed” when it wasn’t). Prefer try-with-resources for anything AutoCloseable; it closes reliably and reduces the need to log in cleanup blocks, keeping logs truthful and concise.
Why isn’t debugging enough, and how do logs complement it?Debugging is real-time and interactive; you can step through code locally. In production you can’t attach a debugger—logs are your past-tense record. Good exception logging (messages, levels, stack traces, causes) is essential for diagnosing issues where debugging isn’t possible.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Java Logging ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Java Logging ebook for free