The Chain of Responsibility pattern is a behavioral design pattern that decouples the sender of a request from its receiver by giving multiple objects a chance to handle the request. These objects are linked into a chain, and the request travels along this chain until one of the handlers processes it. This approach allows you to add or modify handlers without affecting the client code.
Motivating Example: Purchase Approval System
Consider a Supply Chain Management (SCM) system where purchase requests must be approved based on their amount. The approval rules are as follows:
- Director can approve requests under $5,000.
- Vice President can approve requests under $10,000.
- President can approve requests under $50,000.
- Requests of $50,000 or more require board approval.
A naive implementation would place all logic in a single class, leading to code that violates the Single Responsibility and Open/Closed principles. Any change to approval limits or the addition of a new approver would require modifying and retesting the entire class. The Chain of Responsibility pattern offers a more flexible solution.
Pattern Structure

The pattern consists of the following key participants:
- Handler (Abstract): Declares an interface for handling requests. It typically holds a reference to the next handler in the chain (the successor).
- ConcreteHandler: Implements the request handling method. It either processes the request if it can, or forwards it to its successor.
- Client: Initiates the request and configures the chain of handlers.
Implementation in Java
We can implement the purchase approval system using the Chain of Responsibility pattern. First, define the request class:
class PurchaseRequest {
private double amount;
private int number;
private String purpose;
public PurchaseRequest(double amount, int number, String purpose) {
this.amount = amount;
this.number = number;
this.purpose = purpose;
}
public double getAmount() { return amount; }
public int getNumber() { return number; }
public String getPurpose() { return purpose; }
}
Next, define the abstract handler:
abstract class Approver {
protected String name;
protected Approver nextApprover;
public Approver(String name) {
this.name = name;
}
public void setNextApprover(Approver next) {
this.nextApprover = next;
}
public abstract void processRequest(PurchaseRequest request);
}
Now, implement the concrete handlers:
class Director extends Approver {
public Director(String name) { super(name); }
@Override
public void processRequest(PurchaseRequest request) {
if (request.getAmount() < 5000) {
System.out.println("Director " + name + " approved purchase #" + request.getNumber());
} else if (nextApprover != null) {
nextApprover.processRequest(request);
}
}
}
class VicePresident extends Approver {
public VicePresident(String name) { super(name); }
@Override
public void processRequest(PurchaseRequest request) {
if (request.getAmount() < 10000) {
System.out.println("VP " + name + " approved purchase #" + request.getNumber());
} else if (nextApprover != null) {
nextApprover.processRequest(request);
}
}
}
class President extends Approver {
public President(String name) { super(name); }
@Override
public void processRequest(PurchaseRequest request) {
if (request.getAmount() < 50000) {
System.out.println("President " + name + " approved purchase #" + request.getNumber());
} else if (nextApprover != null) {
nextApprover.processRequest(request);
}
}
}
class Congress extends Approver {
public Congress(String name) { super(name); }
@Override
public void processRequest(PurchaseRequest request) {
System.out.println("Board of Directors approved purchase #" + request.getNumber());
}
}
Finally, the client configures the chain and sends requests:
public class Client {
public static void main(String[] args) {
Approver director = new Director("Alice");
Approver vp = new VicePresident("Bob");
Approver president = new President("Charlie");
Approver board = new Congress("Board");
director.setNextApprover(vp);
vp.setNextApprover(president);
president.setNextApprover(board);
PurchaseRequest req1 = new PurchaseRequest(4000, 1001, "Office supplies");
director.processRequest(req1);
PurchaseRequest req2 = new PurchaseRequest(7000, 1002, "New laptops");
director.processRequest(req2);
PurchaseRequest req3 = new PurchaseRequest(25000, 1003, "Server upgrade");
director.processRequest(req3);
PurchaseRequest req4 = new PurchaseRequest(60000, 1004, "Building renovation");
director.processRequest(req4);
}
}
Expected Output:
Director Alice approved purchase #1001
VP Bob approved purchase #1002
President Charlie approved purchase #1003
Board of Directors approved purchase #1004
Adding a New Handler
If a new role, such as Manager (handles requests up to $8,000), is needed, simply create a new class and adjust the chain in the client:
class Manager extends Approver {
public Manager(String name) { super(name); }
@Override
public void processRequest(PurchaseRequest request) {
if (request.getAmount() < 8000) {
System.out.println("Manager " + name + " approved purchase #" + request.getNumber());
} else if (nextApprover != null) {
nextApprover.processRequest(request);
}
}
}
// In Client.main():
Approver manager = new Manager("Diana");
director.setNextApprover(manager);
manager.setNextApprover(vp);
// rest of chain remains the same
This change requires no modification to existing handler classes, adhering to the Open/Closed Principle.
Pure vs. Impure Chain of Responsibility
- Pure Chain: Each handler either fully processes the request or passes it entirely to the next handler. A request must be handled by exactly one handler.
- Impure Chain: Handlers can process part of request and still pass it along. A request might not be handled at all. This is seen in event bubbling mechanisms in JavaScript.
Advantages and Disadvantages
Advantages:
- Reduces coupling between sender and receiver.
- Simplifies object connections by only requiring knowledge of the next handler.
- Allows dynamic addition or reordering of handlers at runtime.
- Facilitates adding new handlers without changing existing code.
Disadvantages:
- No guarantee that a request will be handled; it may reach the end of the chain unprocessed.
- Long chains can impact performance and complicate debugging.
- Improper chain configuration can lead to circular references and infinite loops.
When to Use
- Multiple objects can handle a request, and the appropriate handler is determined at runtime.
- You want to decouple the request sender from multiple potential receivers.
- The set of handlers can change dynamically.
Exercise
Design a leave approval system for an OA application using the Chain of Responsibility pattern:
- Director approves leaves < 3 days.
- Manager approves leaves < 10 days.
- General Manager approves leaves < 30 days.
- Leaves ≥ 30 days are rejected.