Pass a request along a chain of handlers. Each handler decides to process the request or pass it to the next handler in the chain.
Chain of Responsibility is a behavioral design pattern that lets you pass a request along a chain of handler objects. Each handler checks if it can process the request. If yes, it handles it. If no, it passes the request to the next handler.
Think of a customer support escalation: your ticket goes to a Level 1 agent first. If they can't solve it, it escalates to Level 2, then Level 3, then management. Each level either handles it or passes it on — without the customer caring who handles it.
This decouples the sender (who creates the request) from the receiver (who processes it). The sender doesn't know which handler will process it — or even how many handlers there are.
Key Idea: Build a pipeline of handlers. Each handler either processes the request or passes it to the next. The sender is completely decoupled from which handler does the work.
Declares the method for handling requests and optionally the method for setting the next handler (setNext). All concrete handlers implement this interface.
Optional abstract class that stores the next handler reference and implements the default "pass to next" behaviour. Concrete handlers extend this and only override what they need.
Checks if it can handle the request. If yes, processes it. If no, calls the next handler. Each handler has its own criteria for what it can handle.
Builds the chain by linking handlers with setNext(), then sends the request to the first handler. The client doesn't know which handler will ultimately process the request.
Without Chain of Responsibility, the sender must know all possible handlers and route to them with conditionals:
// Sender knows about every handler — tightly coupled
void routeTicket(Ticket t) {
if (t.priority == LOW) l1.handle(t);
else if (t.priority == MED) l2.handle(t);
else if (t.priority == HIGH) l3.handle(t);
else mgmt.handle(t);
// Add new level → edit this method
}// Sender just fires — chain handles the routing l1.process(ticket); // l1 → l2 → l3 → management — sender doesn't care which handles it // Add new level → add a new handler, insert into chain
There are two common flavours of how a chain processes requests:
The first handler that can process the request handles it and the chain stops. Used for routing — exactly one handler should respond.
// Only the matching handler responds
l1.process(ticket); // L1 handles LOW tickets
// L2+ never see itExample: Support escalation, approval routing
Every handler in the chain processes the request in order, regardless of what others did. Used for middleware pipelines.
// Every handler runs in sequence rateLimit → auth → logging → handler // All run for every request
Example: HTTP middleware, event filters
Request flows through the chain
// Chain of Responsibility — Support Ticket Escalation
// ─── Handler interface ────────────────────────────────────────
abstract class SupportHandler {
private SupportHandler next;
// Fluent setter — enables chaining: h1.setNext(h2).setNext(h3)
public SupportHandler setNext(SupportHandler next) {
this.next = next;
return next;
}
// Template method — subclasses override canHandle() and handle()
public void process(SupportTicket ticket) {
if (canHandle(ticket)) {
handle(ticket);
} else if (next != null) {
System.out.println("[" + getClass().getSimpleName()
+ "] Escalating ticket #" + ticket.getId());
next.process(ticket);
} else {
System.out.println("[Chain] No handler found for ticket #"
+ ticket.getId() + " (Priority: " + ticket.getPriority() + ")");
}
}
protected abstract boolean canHandle(SupportTicket ticket);
protected abstract void handle(SupportTicket ticket);
}
// ─── Data object ──────────────────────────────────────────────
class SupportTicket {
private int id;
private String priority; // "LOW", "MEDIUM", "HIGH", "CRITICAL"
private String issue;
public SupportTicket(int id, String priority, String issue) {
this.id = id;
this.priority = priority;
this.issue = issue;
}
public int getId() { return id; }
public String getPriority() { return priority; }
public String getIssue() { return issue; }
}
// ─── Concrete Handlers ────────────────────────────────────────
class Level1Support extends SupportHandler {
@Override
protected boolean canHandle(SupportTicket t) {
return t.getPriority().equals("LOW");
}
@Override
protected void handle(SupportTicket t) {
System.out.println("[L1 Support] Resolving ticket #" + t.getId()
+ " — " + t.getIssue());
}
}
class Level2Support extends SupportHandler {
@Override
protected boolean canHandle(SupportTicket t) {
return t.getPriority().equals("MEDIUM");
}
@Override
protected void handle(SupportTicket t) {
System.out.println("[L2 Support] Resolving ticket #" + t.getId()
+ " — " + t.getIssue());
}
}
class Level3Support extends SupportHandler {
@Override
protected boolean canHandle(SupportTicket t) {
return t.getPriority().equals("HIGH");
}
@Override
protected void handle(SupportTicket t) {
System.out.println("[L3 Engineer] Resolving ticket #" + t.getId()
+ " — " + t.getIssue());
}
}
class ManagementSupport extends SupportHandler {
@Override
protected boolean canHandle(SupportTicket t) {
return t.getPriority().equals("CRITICAL");
}
@Override
protected void handle(SupportTicket t) {
System.out.println("[Management] Handling CRITICAL ticket #" + t.getId()
+ " — " + t.getIssue());
}
}
// ─── Client — builds the chain ────────────────────────────────
public class Application {
public static void main(String[] args) {
// Build chain: L1 → L2 → L3 → Management
SupportHandler l1 = new Level1Support();
l1.setNext(new Level2Support())
.setNext(new Level3Support())
.setNext(new ManagementSupport());
// Tickets automatically routed to the right handler
l1.process(new SupportTicket(101, "LOW", "Password reset"));
l1.process(new SupportTicket(102, "MEDIUM", "App crash on login"));
l1.process(new SupportTicket(103, "HIGH", "Data corruption bug"));
l1.process(new SupportTicket(104, "CRITICAL", "Production server down"));
// Output:
// [L1] Resolving #101
// [L1] Escalating → [L2] Resolving #102
// [L1] Escalating → [L2] Escalating → [L3] Resolving #103
// [L1] Escalating → ... → [Management] Handling CRITICAL #104
}
}When a request should be handled by the first capable handler in a hierarchy — support tickets, approval workflows, incident routing.
SupportEscalation, ExpenseApproval, IncidentRouterWhen a request must pass through a series of processing steps — authentication, rate limiting, logging, caching — each step independent.
ExpressMiddleware, DjangoMiddleware, HttpFilterWhen UI events bubble up through a component hierarchy — a click handled by a button, if not then the panel, if not then the window.
DOM event bubbling, Android touch eventsWhen input must pass through multiple independent validation rules — each rule either rejects or passes to the next.
FormValidator, InputSanitizer, SchemaCheckerWhen log messages are routed to different outputs based on severity — DEBUG to console, WARNING to file, ERROR to alerting service.
ConsoleLogger, FileLogger, AlertLoggerJava Servlet Filters form a chain — each filter in web.xml is linked into a FilterChain. Each filter calls chain.doFilter() to pass the request to the next filter, or returns early to block it.
app.use(middleware) builds a Chain of Responsibility. Each middleware calls next() to pass to the next one, or returns a response to stop the chain. Rate limiters, auth, CORS all use this.
Python's logging.Logger propagates log records up the logger hierarchy. Each logger in the chain can handle (with its handlers) and optionally propagate to the parent — a classic CoR implementation.
Browser events bubble up through the DOM tree — from the target element to its parent, grandparent, up to the document. Each element can handle or let it bubble — Chain of Responsibility in the browser.