Table of Contents


Item 69. Use exceptions only for truly exceptional conditions

Use exceptions only for exceptional conditions

A common misuse is using exceptions for normal loop control:

try {
    int i = 0;
    while(true)
        range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {

}

This style is wrong. Exceptions are designed for exceptional conditions, not regular control flow. try-catch can also limit JVM optimization opportunities. Use ordinary iteration:

for (Mountain m : range)
    m.climb();

A well-designed API should not force clients to use exceptions in normal paths. Prefer state-check methods, optional return values, or sentinel values where appropriate.

Example: in Iterable, hasNext is the state-check method and next is state-dependent.

  • In concurrent access without external synchronization, optional/sentinel results can be safer, because state may change between state check and dependent call.
  • In performance-sensitive paths where state-check duplicates expensive work, optional/sentinel results may be preferable.
  • Otherwise, state-check APIs are often more readable and easier to debug.
Exceptions are designed for exceptional situations.



Item 70. Use checked exceptions for recoverable conditions and runtime exceptions for programming errors

Use checked exceptions for recoverable conditions and runtime exceptions for programming errors

The rule is simple: if callers can reasonably recover, use checked exceptions. Checked exceptions force callers to handle (try-catch) or propagate (throw) them.

Unchecked exceptions (runtime exceptions and errors) are typically for bugs or unrecoverable conditions, where catching often adds little value.

You can subclass Throwable directly, but types that do not extend Exception, RuntimeException, or Error usually add confusion and are not recommended. Also, throwable message formats can differ across JVMs/releases, so avoid strict assumptions about output format.

Use checked exceptions when recovery is possible; use unchecked exceptions for programming errors.



Item 71. Avoid unnecessary use of checked exceptions

Avoid unnecessary use of checked exceptions

Checked exceptions can improve robustness when used correctly, but overuse makes APIs harder to consume.

If a method declares a checked exception, callers must catch or propagate it. Also, in Java 8, methods that throw checked exceptions cannot be used directly in streams.

How to choose between checked and unchecked?

  • If failure can occur despite correct API usage and callers can take meaningful action, checked exceptions are appropriate.
  • Otherwise, unchecked exceptions are usually better.

Ways to avoid checked exceptions when appropriate

When adding a new standalone checked exception, reconsider alternatives:

Return Optional with an appropriate result type

Instead of throwing a checked exception, return an empty optional. Tradeoff: you lose rich failure details.

Split API into state-check and action

// before
try {
    obj.action(args);
} catch (TheCheckedException e) {
    // handle exception
}
// after
if (obj.actionPermitted(args)) {
    obj.action(args);
} else {
    // handle alternative path
}

This adds a branch but can simplify callers. However, if object state can change between actionPermitted and action in concurrent access without external synchronization, this refactoring may not be safe.

Use checked exceptions only where they are truly necessary.



Item 72. Favor the use of standard exceptions

Favor the use of standard exceptions

Exceptions should be reused too. Fewer exception classes reduce memory footprint, class-loading overhead, and improve readability through familiar semantics.

Commonly reused exceptions

  • IllegalArgumentException
    • Argument value is invalid
    • For null specifically, prefer NullPointerException
  • IllegalStateException
    • Object state is inappropriate for method invocation
  • NullPointerException
    • Null passed where not allowed
  • IndexOutOfBoundsException
    • Index exceeds valid range
  • ConcurrentModificationException
    • Illegal concurrent modification detected
  • UnsupportedOpertionException
    • Operation is not supported

You may extend standard exceptions to add context, but creating many custom exceptions is usually discouraged, partly because exception types are serializable and carry long-term compatibility burden.

Java libraries already provide enough standard exceptions for most APIs.



Item 73. Throw exceptions appropriate to the abstraction

Throw exceptions appropriate to the abstraction

If a high-level method leaks a low-level exception (for example IndexOutOfBoundsException), it breaks abstraction boundaries. Use exception translation: catch lower-level exceptions and throw higher-level exceptions aligned with your API.

try {
    // use lower-level abstraction
} catch (LowerLevelException e) {
    // translate to higher-level abstraction
    throw new HigherLevelException(...);
}

If lower-level cause helps debugging, attach it using exception chaining:

try {
    // use lower-level abstraction
} catch (LowerLevelException e) {
    // include lower-level cause
    throw new HigherLevelException(e);
}

Exception translation is better than blindly propagating low-level details, but do not overuse it. Prefer preventing lower-level failures when possible, for example by validating inputs in higher layers. As a fallback, you can log and contain failures where appropriate, so users are not exposed to internal exceptions while developers keep diagnostics.

Use exception translation and exception chaining appropriately.



Item 74. Document all exceptions thrown by each method

Document all exceptions thrown by each method

Thrown exceptions are part of method contract. They are essential for correct API usage, so they must be documented carefully.

Document every exception a method can throw.



Item 75. Include failure-capture information in detail messages

Include failure-capture information in detail messages

When an exception escapes and fails a program, the system prints stack trace including Throwable.toString() output:

public String toString() {
    String s = getClass().getName();
    String message = getLocalizedMessage();
    return (message != null) ? (s + ": " + message) : s;
}

Good detail messages should capture relevant state values at failure time. For example, for IndexOutOfBoundsException, include min/max bounds and actual index.

Do not make messages verbose without value. Include information that helps failure analysis, and never include sensitive data such as passwords or encryption keys.

Detail messages should make failure causes diagnosable.



Item 76. Strive for failure atomicity

Strive for failure atomicity

Failure atomicity means: if a method fails, object state remains as it was before the call. Whenever possible, preserve this property.

Even when a method fails, the object should ideally remain in its pre-call state.



Item 77. Don’t ignore exceptions

Don’t ignore exceptions

If an API declares exceptions, callers are expected to handle them appropriately. An empty catch block defeats that contract:

try {
    ...
} catch (SomeException e) { }

There are rare cases where ignoring is acceptable. For example, when closing FileInputStream, recovery may not be meaningful once all needed data is already read.

Even then, if you choose to ignore:

  • document the reason in comments
  • rename the exception variable clearly (for example ignored)
try {
    ...
} catch (SomeException ignored) {
    // intentionally ignored; keep rationale and relevant logging
}
If catch blocks are empty, exceptions lose their purpose.