Definition
- Chain of Responsibility – a behavioral pattern that passes a request along a chain of handler objects.
- Handler – an abstract class or interface declaring a method to handle the request and holding a reference to the next handler.
- ConcreteHandler – implements the handling logic and decides whether to process or forward.
- Decoupling – the sender has no idea which handler will ultimately process the request.
- Extensibility – adding a new handler is inserting a new link in the chain with zero changes to existing handlers.
Imagine Rajesh walks into National Bank of India and asks to approve a large transfer of twelve lakh rupees. The junior clerk can approve up to fifty thousand, so the clerk passes it to the branch officer who can approve up to five lakh. Still not enough, so the officer passes it to the branch manager who can approve up to twenty lakh. The manager approves it. Rajesh never had to know who would approve it: the request travelled up the chain until someone with enough authority handled it.
Understanding the Concepts
What is the Chain of Responsibility pattern?
Chain of Responsibility is a behavioral pattern where a request is passed along a chain of handler objects. Each handler decides either to process the request or to pass it to the next handler. The sender never knows which handler will ultimately deal with it.
package com.bank.chain;
import java.math.BigDecimal;
public abstract class ApprovalHandler {
private ApprovalHandler next;
protected final BigDecimal limit;
protected ApprovalHandler(BigDecimal limit) {
this.limit = limit;
}
public void setNext(ApprovalHandler next) {
this.next = next;
}
public void approve(TransferRequest request) {
if (request.getAmount().compareTo(limit) <= 0) {
doApprove(request);
} else if (next != null) {
next.approve(request);
} else {
reject(request);
}
}
protected abstract void doApprove(TransferRequest request);
private void reject(TransferRequest request) {
System.out.println("REJECTED: No handler can approve INR "
+ request.getAmount());
}
}
Why does the Chain of Responsibility exist?
Without this pattern, the approval logic would be one giant if-else method that every developer is afraid to touch. Adding a new approval tier means editing that method, which risks breaking existing tiers.
// Without chain: monolithic if-else (bad)
if (amount <= 50000) { clerk.approve(); }
else if (amount <= 500000) { officer.approve(); }
else if (amount <= 2000000) { manager.approve(); }
// Adding a new tier means editing this code
With the chain, each tier is its own class, and adding a new tier is just inserting a new handler.
How do you implement concrete handlers?
Each handler extends the abstract handler and provides its own approval logic.
package com.bank.chain;
import java.math.BigDecimal;
public class ClerkHandler extends ApprovalHandler {
public ClerkHandler() { super(new BigDecimal("50000")); }
@Override
protected void doApprove(TransferRequest request) {
System.out.println("Clerk approved transfer of INR "
+ request.getAmount());
}
}
public class OfficerHandler extends ApprovalHandler {
public OfficerHandler() { super(new BigDecimal("500000")); }
@Override
protected void doApprove(TransferRequest request) {
System.out.println("Officer approved transfer of INR "
+ request.getAmount());
}
}
public class ManagerHandler extends ApprovalHandler {
public ManagerHandler() { super(new BigDecimal("2000000")); }
@Override
protected void doApprove(TransferRequest request) {
System.out.println("Manager approved transfer of INR "
+ request.getAmount());
}
}
How do you wire and use the chain?
Build the chain by setting the next handler for each link, then send requests to the first handler.
package com.bank.demo;
import com.bank.chain.*;
import java.math.BigDecimal;
public class ChainDemo {
public static void main(String[] args) {
ApprovalHandler clerk = new ClerkHandler();
ApprovalHandler officer = new OfficerHandler();
ApprovalHandler manager = new ManagerHandler();
clerk.setNext(officer);
officer.setNext(manager);
clerk.approve(new TransferRequest("SAV2001", new BigDecimal("30000")));
clerk.approve(new TransferRequest("SAV2001", new BigDecimal("300000")));
clerk.approve(new TransferRequest("SAV2001", new BigDecimal("1200000")));
clerk.approve(new TransferRequest("SAV2001", new BigDecimal("5000000")));
}
}
Clerk approved transfer of INR 30000
Officer approved transfer of INR 300000
Manager approved transfer of INR 1200000
REJECTED: No handler can approve INR 5000000
How is Chain of Responsibility different from a pipeline?
In pure CoR (approval escalation), exactly one handler processes the request. In a pipeline (validation), every handler runs in sequence and all checks must pass.
| Aspect | Pure CoR | Pipeline |
|---|---|---|
| Who processes? | First capable handler | Every handler in sequence |
| Example | Approval escalation | Validation checks |
| Stop condition | One handler handles it | One handler rejects it |
// Pipeline variant: every handler checks one rule
public abstract class ValidationHandler {
private ValidationHandler next;
public void validate(TransferRequest request) {
check(request); // throws if invalid
if (next != null) next.validate(request);
}
protected abstract void check(TransferRequest request);
}
What happens if no handler can process the request?
If the chain ends without any handler processing the request, it can silently vanish. In banking this is dangerous. Always add a terminal handler or an explicit reject step.
// Wrong: request silently vanishes
if (next != null) { next.approve(request); }
// else: nothing happens (dangerous)
// Correct: explicit rejection at chain end
if (next != null) {
next.approve(request);
} else {
log.severe("No handler for INR " + request.getAmount());
throw new UnsupportedOperationException(
"Transfer exceeds all approval limits");
}
What are real-world examples?
Spring Security's filter chain is Chain of Responsibility: each Filter either handles the request or calls chain.doFilter(request, response). The Servlet FilterChain is the same pattern at framework level.
// Spring Security filter chain (conceptual)
public class AuthenticationFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException {
if (isAuthenticated(req)) {
chain.doFilter(req, resp); // pass to next filter
} else {
resp.sendError(401); // handle here, stop chain
}
}
}
How do you make the chain dynamic?
The chain can be built from configuration, so different branches or times of day get different approval tiers. This makes the pattern highly flexible.
// Build chain from configuration
List<ApprovalHandler> handlers = config.getApprovalTiers().stream()
.map(tier -> createHandler(tier))
.collect(Collectors.toList());
for (int i = 0; i < handlers.size() - 1; i++) {
handlers.get(i).setNext(handlers.get(i + 1));
}
// Now the chain is configured dynamically
What should a senior engineer know?
Each handler should be focused on a single decision, which respects the Single Responsibility Principle and keeps the chain easy to reorder. When the business adds a new tier, like a regional head for transfers above twenty lakh, Rajesh writes one new handler class and wires it at the end. No existing handler code is touched, which is the Open/Closed Principle in action.
// Adding a new tier: one new class, zero changes to existing code
public class RegionalHeadHandler extends ApprovalHandler {
public RegionalHeadHandler() { super(new BigDecimal("10000000")); }
@Override
protected void doApprove(TransferRequest request) {
System.out.println("Regional Head approved INR " + request.getAmount());
}
}
manager.setNext(new RegionalHeadHandler()); // just wire it in
Visual Overview
Transfer Request (INR 12,00,000)
│
▼
┌──────────────┐ limit: 50,000
│ Clerk │──→ cannot handle
└──────┬───────┘
│ forwards
▼
┌──────────────┐ limit: 5,00,000
│ Officer │──→ cannot handle
└──────┬───────┘
│ forwards
▼
┌──────────────┐ limit: 20,00,000
│ Manager │──→ APPROVED
└──────────────┘
Pipeline variant (validation)
─────────────────────────────
Request ──→ AmountValidator ──→ KycValidator ──→ LimitValidator
(check amount) (check KYC) (check limit)
All must pass for the request to proceed
Key Takeaway
Chain of Responsibility is a behavioral pattern where a request is passed along a chain of handler objects, and each handler either processes the request or forwards it to the next. The real power is decoupling: the sender does not need to know which handler will ultimately deal with it, which keeps the sender simple and makes the handler set easy to extend and reorder.
In the banking project at National Bank of India, Rajesh uses this pattern for transfer approval escalation. Different tiers: a clerk approves up to fifty thousand, an officer up to five lakh, and a manager up to twenty lakh. Instead of one giant if-else method, each tier is its own handler class. The teller submits the request to the first handler, and it travels up the chain until someone with enough authority approves it.
The benefit is clean extensibility. When the business adds a regional head tier for transfers above twenty lakh, Rajesh writes one new handler class and wires it at the end. No existing code is touched, which is the Open/Closed Principle. Each handler also has a single, clear responsibility, so the code is easy to test.
One production detail to always stress is the terminal case. If a request exceeds every handler's authority and there is no fallback, it can silently vanish, which is dangerous in finance. A real-world example of this pattern at framework level is the Spring Security filter chain, where each filter either handles the request or calls the chain to pass it forward.
Remember one sentence: Chain of Responsibility passes a request down a line of handlers, each processing or forwarding, so the sender is decoupled from the receivers and the chain is freely extensible.
---