The Chain of Responsibility pattern passes a request along a chain of handlers. Each handler decides either to process the request or to pass it to the next handler in the chain. This decouples senders from receivers and gives multiple objects a chance to handle the request.

Intent and Motivation

Intent: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

Motivation: An HTTP request might need logging, authentication, rate limiting, and routing — but not every handler applies to every request. Instead of a central dispatcher with giant if-else chains, each concern is a handler that either processes and stops, or forwards to next. Adding a new handler (e.g., CORS) means inserting a link in the chain without modifying existing handlers.

This pattern models approval workflows, event propagation, and middleware stacks.

Structure (UML-like)

  ┌──────────┐  handleRequest()  ┌──────────────────┐    next    ┌──────────────────┐
│  Client  │ ────────────────► │     Handler      │ ────────► │     Handler      │ ──► ...
└──────────┘                   ├──────────────────┤           ├──────────────────┤
                               │ - successor      │           │ - successor      │
                               │ + handleRequest()│           │ + handleRequest()│
                               │ + setNext()      │           └──────────────────┘
                               └──────────────────┘
  

Participants:

  • Handler — defines interface for handling requests and storing the next handler.
  • ConcreteHandler — processes requests it is responsible for; otherwise forwards to successor.
  • Client — initiates the request to the first handler in the chain.
  • Client (optional) — may also assemble the chain.

Java Example

  abstract class SupportHandler {
    protected SupportHandler next;

    void setNext(SupportHandler next) { this.next = next; }

    abstract void handle(String issue, int severity);

    protected void passToNext(String issue, int severity) {
        if (next != null) next.handle(issue, severity);
        else System.out.println("No handler for: " + issue);
    }
}

class Level1Support extends SupportHandler {
    void handle(String issue, int severity) {
        if (severity <= 1) {
            System.out.println("L1 resolved: " + issue);
        } else {
            passToNext(issue, severity);
        }
    }
}

class Level2Support extends SupportHandler {
    void handle(String issue, int severity) {
        if (severity <= 3) {
            System.out.println("L2 resolved: " + issue);
        } else {
            passToNext(issue, severity);
        }
    }
}

class Level3Support extends SupportHandler {
    void handle(String issue, int severity) {
        System.out.println("L3 escalated: " + issue);
    }
}

// Build chain
SupportHandler chain = new Level1Support();
chain.setNext(new Level2Support());
chain.getClass(); // setup
((Level1Support) chain).setNext(new Level2Support());
Level2Support l2 = new Level2Support();
l2.setNext(new Level3Support());
((Level1Support) chain).setNext(l2);

chain.handle("Login issue", 1);  // L1 resolved
chain.handle("Data corruption", 5); // L3 escalated
  

JavaScript Example

  function createMiddlewareChain(...middlewares) {
  return function dispatch(request, index = 0) {
    if (index >= middlewares.length) return;
    const next = () => dispatch(request, index + 1);
    middlewares[index](request, next);
  };
}

const logger = (req, next) => {
  console.log(`[LOG] ${req.method} ${req.url}`);
  next();
};

const auth = (req, next) => {
  if (!req.token) {
    console.log('[AUTH] Rejected');
    return; // stop chain
  }
  console.log('[AUTH] OK');
  next();
};

const handler = (req, next) => {
  console.log(`[HANDLER] Processing ${req.url}`);
};

const app = createMiddlewareChain(logger, auth, handler);
app({ method: 'GET', url: '/api/data', token: 'abc' });
app({ method: 'GET', url: '/api/public' }); // rejected at auth
  

Real-World Use Cases

Framework / System Usage
Servlet Filters / Spring FilterChain HTTP requests pass through authentication, logging, and CORS filters.
Express.js middleware app.use() chains handlers; each calls next() or terminates.
Apache Camel / AWS Step Functions Message and workflow routing through processing pipelines.
JavaScript event bubbling DOM events propagate from target up through parent elements.
Netty pipeline Channel handlers process network I/O in a chain.
Approval workflows Expense reports escalate from manager → director → VP based on amount.

Pros and Cons

Pros Cons
Reduces coupling — sender does not know which handler processes the request No guarantee any handler will process the request
Add or reorder handlers without changing sender or other handlers Chain traversal can hurt performance for long chains
Single Responsibility — each handler focuses on one concern Debugging request flow through many handlers is challenging
Flexible runtime chain composition Can be hard to observe the full chain (implicit structure)
Supports multiple handlers processing the same request (if designed) Risk of infinite loops if handlers call next() incorrectly

When to Use vs When NOT to Use

Use when:

  • More than one object may handle a request, and the handler is not known in advance.
  • You want to issue a request to one of several objects without specifying the receiver explicitly.
  • The set of handlers should be specified dynamically at runtime.
  • Processing logic should be modular and independently testable.

Do NOT use when:

  • Every request must be handled — ensure a terminal handler or default fallback.
  • Only one handler will ever process requests (use direct call or Strategy).
  • Chain order is critical and must be enforced at compile time (explicit orchestration may be clearer).
  • Performance requires direct dispatch without traversal overhead.

Common Mistakes

  1. Forgetting to call next() — silently drops requests in middleware chains.
  2. Calling next() after sending a response — double-processing or “headers already sent” errors.
  3. No terminal handler — requests fall through unhandled with no error.
  4. Circular chain references — infinite loops.
  5. Putting business logic in the chain assembly — chain construction should be separate from handler logic.
  • Composite — chains can be modeled as composites where each handler is a component.
  • Decorator — both chain behavior, but Decorator wraps a single object; CoR passes along a chain.
  • Command — requests can be represented as Command objects passed through the chain.
  • Mediator — centralizes communication; CoR decentralizes it across a chain.
  • Observer — event propagation through parent elements resembles a responsibility chain.