Chain of Responsibility: Request Pipeline
The Problem It Solves
An incoming request needs a variable sequence of checks and handlers: auth, rate limit, validation, caching, business handling. Hardcoding that sequence in the caller couples it to every handler; adding one check edits core flow. Chain of Responsibility lines up handlers, each deciding: handle this, or pass it to the next — the caller knows only the chain’s head.
THE ESCALATION MODEL
request ──► AuthHandler ──► RateLimitHandler ──► Validation ──► BusinessHandler
│ pass if ok │ │ │
│ │ 429 → stop │ bad → stop │ handles → stop
401 → stop (each link either CONSUMES or FORWARDS)
caller wires: auth.setNext(rateLimit).setNext(validation)...
The support-desk analogy maps exactly: L1 answers if qualified, else escalates to L2, else L3. Each tier’s knowledge is local; adding “L0 bot tier” inserts one link without telling the others.
Two Wiring Styles
// CLASSIC — linked objects with explicit next pointer:
abstract class Handler {
private Handler next;
Handler setNext(Handler n) { this.next = n; return n; } // fluent wiring
void handle(Request r) {
if (!process(r) && next != null) next.handle(r); // false = not consumed
}
protected abstract boolean process(Request r);
}
// LIST-BASED — pipeline as ordered collection (frameworks prefer this):
class Pipeline {
private final List<Middleware> middlewares;
Response execute(Request r) {
Response resp = Response.ok();
for (var mw : middlewares) { // order = list order, explicit
resp = mw.apply(r, resp);
if (resp.isTerminal()) break;
}
return resp;
}
}
List-based wins for configurability (order from config, per-route pipelines); classic form wins when handlers themselves decide routing.
Real-World Sightings
- Servlet filters / Spring interceptors — literally called filter chains.
- Netty channel pipeline, Express.js middleware, Nginx processing phases.
- Exception matching up JDK call stacks is conceptually the same walk.
Design Rules That Prevent Production Pain
| Rule | Why |
|---|---|
| Guarantee a terminal handler | Chains that fall off the end silently drop requests |
| Make ordering explicit & documented | ”Auth before rate-limit” bugs are order-dependent by nature |
| Cap chain length / detect cycles | Mis-wired a.setNext(b); b.setNext(a) = infinite loop |
| Decide consumption semantics up front | ”handled” vs “enriched-and-passed” are different contracts |
vs Decorator & Strategy
| Pattern | Question it answers |
|---|---|
| Chain | Which handler processes this request? (dynamic selection by walking) |
| Decorator | All layers wrap in fixed stack (no selection — everyone runs) |
| Strategy | Caller picks algorithm directly |
Chain ≈ decorator where each node may terminate traversal.
Trade-offs
- Gains: caller decoupled from handler identity; runtime-reconfigurable sequences; single-responsibility links.
- Costs: request can vanish silently if no link consumes it; debugging traversals needs logging at each hop; deep chains add per-hop overhead (trivial locally, real at framework scale).
Interview Framing
- Asked to design rate limiting/auth middleware, drawing the pipeline with terminal-guarantee + explicit ordering covers both pattern and production axes.
Premium Content
Unlock Chain of Responsibility and all premium lessons with a subscription.
From ₹199.99/year — See plans