Overview

7 Handling failure with grace

Failure is inevitable in software, so graceful handling must be designed deliberately rather than added as an afterthought. The chapter frames resilience as part of code quality: systems should anticipate what can go wrong, respond in ways that preserve user trust, avoid exposing sensitive technical details, and leave developers enough diagnostic information to understand the root cause.

The chapter distinguishes four major failure categories. Business logic errors are expected violations of rules or user constraints and should be communicated clearly so users can recover. Technical issues come from infrastructure or external dependencies and should be logged, translated, retried, or handled with fallbacks when appropriate. Programming errors are defects in the code and should be found through testing and fixed rather than normalized through catch blocks. Fatal errors compromise process stability and usually require fail-fast behavior, infrastructure correction, or deep diagnostic investigation rather than ordinary recovery.

Much of graceful failure handling comes from avoiding common antipatterns: unprotected entry points, broad generic catches, empty catch blocks, careless logging, duplicated logs, logs that hide the original error, raw technical exceptions leaking across boundaries, and missing fallback strategies. The chapter recommends validating and sanitizing inputs early, catching specific failures in tight code blocks, logging only actionable events with useful context and without sensitive data, preserving original causes when wrapping exceptions, using domain-specific exceptions or explicit result types, and applying strategies such as retries, circuit breakers, cached responses, degraded modes, rollback, or requeuing when they fit the business context.

Summary

  • Failures fall into four categories: business logic errors, technical issues, programming errors, and fatal errors, and each demands a different handling strategy.
    • Business logic errors are violations of business rules.
    • Technical issues are infrastructure or external service failures.
    • Programming errors are bugs in the code that must be fixed during development.
    • Fatal errors are generally not recoverable within the failing process.
  • Protect the system boundaries by validating and sanitizing incoming data, enforcing authentication and authorization, and rejecting invalid or unauthorized requests as early as possible.
  • Avoid generic catches unless there is a clearly justified reason.
  • Keep the protected block narrow, so the failure path remains clear and readable.
  • Log only when it adds diagnostic value, at the right level, and in the right place.
  • A good log message includes useful context, avoids sensitive data, and helps explain what went wrong.
  • In production, logs should be routed to outputs that support collection, persistence, and monitoring.
  • Technical failures should not cross architectural boundaries as raw implementation details. Translate low-level failures into meaningful domain exceptions or controlled responses.
  • Preserve the original cause when translating an exception, so diagnosis remains possible.
  • A thoughtful error-handling strategy should be chosen according to the problem at hand.

FAQ

What are the four main categories of failure described in chapter 7?

The chapter groups failures into four fundamental categories:

  • Business logic errors: violations of business rules or expected usage constraints, such as invalid form input or an expired discount code.
  • Technical issues: failures caused by infrastructure, external systems, configuration, databases, files, networks, or other dependencies.
  • Programming errors: defects in the code itself, such as invalid assumptions, broken invariants, type errors, or out-of-bounds access.
  • Fatal errors: critical runtime failures that compromise process stability and usually cannot be recovered from inside the failing process.
How should business logic errors be handled?

Business logic errors should be anticipated and handled explicitly. They are not bugs; they are expected deviations from valid user behavior or business rules. The system should guide the user, explain what went wrong in understandable terms, and help them take corrective action.

They can be represented through domain-specific exceptions or explicit result types. The response should be clear and useful without exposing technical details such as stack traces, file paths, SQL fragments, or internal implementation information.

Why should technical issues not be exposed directly to users or client systems?

Technical issues often contain details about infrastructure, frameworks, libraries, SQL queries, file paths, hostnames, or internal system behavior. Exposing those details can confuse ordinary users and may also give attackers useful information.

Instead, technical issues should usually be logged with enough context for diagnosis, then translated into a domain-specific exception, a controlled error response, or handled through a fallback strategy when appropriate.

What is the difference between technical issues and programming errors?

Technical issues come from outside the code, such as unavailable databases, network timeouts, missing files, expired certificates, or external services failing.

Programming errors come from defects inside the code itself, such as invalid logic, wrong assumptions, null references, invalid casts, division by zero, or accessing an array outside its bounds.

Technical issues may require logging, translation, retry, fallback, or propagation to a higher layer. Programming errors should be detected early through testing and fixed directly in the code rather than treated as normal recoverable failures.

What are fatal errors, and why should they usually not be caught?

Fatal errors are severe runtime failures that compromise the stability of the process. Examples include OutOfMemoryError and StackOverflowError in Java, MemoryError or RecursionError in Python, and OutOfMemoryException or StackOverflowException in C#.

They usually indicate that the failing process cannot safely recover by itself. Continuing after such failures may leave the system corrupted or unreliable. The appropriate response is often fail-fast behavior, infrastructure correction, code fixes, or external recovery through orchestration and restart mechanisms.

What is the “lazy catch” antipattern?

The “lazy catch” antipattern is the use of a broad catch block that captures every possible failure, such as catching Exception or Throwable in Java, using a bare except: in Python, or treating every JavaScript error the same way.

This is problematic because it hides specific failure paths, damages readability, weakens evolvability, may accidentally absorb programming errors, and makes production monitoring less precise. The chapter recommends catching specific expected exceptions and keeping the protected block as tight as possible around a meaningful chunk of code.

Why must application entry points be defended?

Entry points are where the outside world interacts with the application, making them one of the weakest parts of the system. Invalid, unauthorized, or malicious input should be rejected as early as possible before it spreads deeper into the application.

Defending boundaries means applying validation, sanitization, authentication, and authorization. Backend validation is still required even when frontend validation exists, because client-side checks can be bypassed. Proper boundary protection helps prevent functional failures and security vulnerabilities such as injection, cross-site scripting, and broken access control.

When should an error be logged?

An error should be logged when the information is useful for investigation, monitoring, security analysis, or post-mortem diagnosis. Technical issues generally need logging because they reflect abnormal infrastructure or dependency failures.

Business logic errors do not always need logging. For example, a user mistyping an email address is usually not actionable. However, repeated invalid login attempts may indicate credential stuffing or password spraying and should be logged and monitored.

The guiding question is: Is this an abnormal failure that should be investigated if it occurs? If not, logging may only pollute the logs.

What makes a log message useful?

A useful log message should provide enough context to support diagnosis without exposing sensitive data. It usually includes:

  • Relevant contextual information, such as safe input parameters or a transaction ID.
  • A clear description of the operation that failed.
  • A unique message or identifier when useful, so the failure can be traced to one location in the code.
  • The exception object itself, so the root cause and stack trace are preserved.

Log messages should avoid vague text such as “An error occurred” or “Caught exception.” They should also avoid sensitive information such as passwords, credit card numbers, user credentials, internal file paths, or other data that could become a security risk.

What fallback strategies can help an application handle technical failures gracefully?

Fallback strategies allow an application to continue operating in a degraded but acceptable mode when the preferred path fails. Common examples include:

  • Retrying a failed operation when the failure may be temporary.
  • Using cached data when a live data source is unavailable.
  • Switching to an alternative service, such as a secondary email relay.
  • Providing a limited version of a resource, such as thumbnails instead of high-resolution images.
  • Using a safe default value when a calculation cannot be completed.
  • Rolling back completed work when a later step fails.
  • Requeuing work for later processing.
  • Using a circuit breaker to stop overwhelming a failing dependency.

A fallback should never silently hide the original failure. Technical problems must still be logged with enough context to support future diagnosis.

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
  • The Art of Code 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
  • The Art of Code 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
  • The Art of Code ebook for free