Design PatternsBehavioral

Chain of Responsibility

Pass a request along a chain of handlers; each decides to handle it or forward it.

3 min readDesigns · LLD & Patterns

Intent

Avoid coupling the sender of a request to its receiver by giving multiple objects a chance to handle it. Pass the request along until one handles it.

Use when:

  • Multiple handlers can process a request, and you don't want the sender to pick.
  • The set of handlers should be configurable at runtime.
  • You want to model a pipeline (logging, auth, rate limit, business logic).

Real-world Analogy

Customer support: your ticket starts with a Tier 1 agent. If they can't help, it escalates to Tier 2. Tier 2 escalates to engineering. You don't decide who handles it — the chain does.


Structure

StructureClass diagram

Example: HTTP Middleware Chain

public abstract class Handler {
    private Handler next;

    public Handler setNext(Handler h) { this.next = h; return h; }

    public Response handle(Request req) {
        if (next != null) return next.handle(req);
        return Response.ok();
    }
}

class AuthHandler extends Handler {
    public Response handle(Request req) {
        if (req.token == null) return Response.unauthorized();
        return super.handle(req);   // forward
    }
}

class RateLimitHandler extends Handler {
    private final RateLimiter limiter = new RateLimiter(100);
    public Response handle(Request req) {
        if (!limiter.allow(req.clientId)) return Response.tooManyRequests();
        return super.handle(req);
    }
}

class LoggingHandler extends Handler {
    public Response handle(Request req) {
        System.out.println("[REQ] " + req.url);
        Response r = super.handle(req);
        System.out.println("[RES] " + r.status);
        return r;
    }
}

class BusinessHandler extends Handler {
    public Response handle(Request req) {
        return doWork(req);   // terminal — no super.handle()
    }
}

// Wire up
Handler chain = new LoggingHandler();
chain.setNext(new AuthHandler())
     .setNext(new RateLimitHandler())
     .setNext(new BusinessHandler());

Response r = chain.handle(request);

Each handler decides: process? forward? short-circuit?


Variants

Pass-through (every handler runs)

Every handler in the chain processes; none stops it. Useful for logging, metrics, observability.

First-handler-wins

The first handler that can handle the request takes it; others never see it. Good for support tickets, dispatchers.

Branching chain

Handler decides which sub-chain to forward to (rare; usually composite + chain).


Example: Approval Workflow

abstract class Approver {
    protected Approver next;
    public void setNext(Approver a) { this.next = a; }

    public abstract void approve(LeaveRequest r);
}

class Manager extends Approver {
    public void approve(LeaveRequest r) {
        if (r.days <= 5) System.out.println("Manager approved");
        else next.approve(r);
    }
}

class Director extends Approver {
    public void approve(LeaveRequest r) {
        if (r.days <= 15) System.out.println("Director approved");
        else next.approve(r);
    }
}

class CEO extends Approver {
    public void approve(LeaveRequest r) {
        System.out.println("CEO approved");
    }
}

// Wire
Manager m = new Manager();
Director d = new Director();
m.setNext(d);
d.setNext(new CEO());

m.approve(new LeaveRequest(20));   // CEO
m.approve(new LeaveRequest(3));    // Manager

Real-world Examples

Use caseChain
Servlet filters / Express middlewareEach filter sees and forwards
Logging frameworksAppenders / handlers
Java exception handlingcatch blocks try in order
Event bubbling in DOMElement → parent → ancestor
Tax calculatorsState → federal → local
Validation chainsField validators in sequence

Chain of Responsibility vs Decorator

Both wrap and forward. The difference:

PatternGoalTermination
ChainOne handler eventually handles, others passHandler can short-circuit
DecoratorEvery wrapper adds behavior; the inner core always runsAlways reaches the wrapped object

A chain may stop early (auth fails → 401, no more handlers run). A decorator stack always runs end-to-end.


Trade-offs

✅ Pros:

  • Decouples senders from handlers
  • Chain composition is runtime-configurable
  • Single Responsibility per handler
  • Easy to add or reorder handlers

❌ Cons:

  • Request may go unhandled if no terminal handler exists
  • Hard to debug — no static call graph
  • Implicit ordering — easy to mess up
  • Performance: each request walks the chain

Interview Tips

  • Reach for chain when the interviewer describes middleware, escalation, or pipelines with multiple handlers.
  • Distinguish from decorator by termination behavior.
  • Mention concrete examples interviewers know: servlet filters, Express middleware, exception handlers.

Finished reading?

Mark it done to track your progress in Designs.