13. Chain of Responsibility
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
- Forgetting to call
next()— silently drops requests in middleware chains. - Calling
next()after sending a response — double-processing or “headers already sent” errors. - No terminal handler — requests fall through unhandled with no error.
- Circular chain references — infinite loops.
- Putting business logic in the chain assembly — chain construction should be separate from handler logic.
Related Patterns
- 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.