Definition
- Chain of Responsibility – passes a request along a chain of handlers, each deciding to process it or forward to the next.
- Handler – holds a reference to the next handler and a
handle()method. - Validation pipeline – every handler checks one rule; all must pass for the request to proceed.
- Approval workflow – each handler checks its authority; the first capable handler processes and stops.
- Extensibility – adding a new step is inserting a new handler with zero changes to existing ones.
Think of the Chain as a loan approval route at National Bank of India. Rajesh's loan request passes first to the clerk, then the branch manager, then the regional head, each approving within their limit or escalating. The request flows down the chain until someone handles it or it runs out of handlers.
Understanding the Concepts
What is the Chain of Responsibility for validation and approval?
The Chain of Responsibility turns a long if-else or a monolithic validator into a set of small, independent handlers that can be reordered or extended freely. Each handler holds a reference to the next and either processes the request or forwards it.
package com.bank.chain;
import java.math.BigDecimal;
public abstract class ValidationHandler {
private ValidationHandler next;
public void setNext(ValidationHandler next) {
this.next = next;
}
public void validate(TransferRequest request) {
check(request);
if (next != null) {
next.validate(request);
}
}
protected abstract void check(TransferRequest request);
}
How do you implement a validation pipeline?
Each handler checks one rule and either rejects the request (by throwing) or forwards it. All handlers must pass for the request to proceed.
package com.bank.chain;
import java.math.BigDecimal;
public class AmountValidator extends ValidationHandler {
@Override
protected void check(TransferRequest request) {
if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException(
"Amount must be positive, got: " + request.getAmount());
}
}
}
public class KycValidator extends ValidationHandler {
@Override
protected void check(TransferRequest request) {
if (!request.isKycComplete()) {
throw new IllegalStateException(
"KYC not complete for account: " + request.getFromId());
}
}
}
public class DailyLimitValidator extends ValidationHandler {
private static final BigDecimal DAILY_LIMIT = new BigDecimal("500000");
@Override
protected void check(TransferRequest request) {
if (request.getAmount().compareTo(DAILY_LIMIT) > 0) {
throw new IllegalArgumentException(
"Amount INR " + request.getAmount()
+ " exceeds daily limit of INR " + DAILY_LIMIT);
}
}
}
How do you wire the validation chain?
Build the chain by linking handlers, then send requests to the first handler.
package com.bank.demo;
import com.bank.chain.*;
import java.math.BigDecimal;
public class ValidationChainDemo {
public static void main(String[] args) {
ValidationHandler amount = new AmountValidator();
ValidationHandler kyc = new KycValidator();
ValidationHandler limit = new DailyLimitValidator();
amount.setNext(kyc);
kyc.setNext(limit);
TransferRequest request = new TransferRequest(
"SAV2001", "SAV3002",
new BigDecimal("100000"), true);
try {
amount.validate(request);
System.out.println("All validations passed");
} catch (Exception e) {
System.out.println("Validation failed: " + e.getMessage());
}
}
}
All validations passed
How do you implement an approval workflow?
In approval mode, each handler checks its authority. The first capable handler processes and stops.
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 {
System.out.println("REJECTED: exceeds all approval limits");
}
}
protected abstract void doApprove(TransferRequest request);
}
ApprovalHandler clerk = new ClerkApproval();
ApprovalHandler officer = new OfficerApproval();
ApprovalHandler manager = new ManagerApproval();
clerk.setNext(officer);
officer.setNext(manager);
clerk.approve(new TransferRequest("SAV2001", "SAV3002",
new BigDecimal("300000"), true));
Officer approved transfer of INR 300000
How is validation different from approval?
In a validation pipeline, every handler runs in sequence: all checks must pass. In an approval workflow, exactly one handler processes: the first one with enough authority.
| Aspect | Validation Pipeline | Approval Workflow | |---|---|---| | Who processes? | Every handler runs | First capable handler | | Failure | Any handler rejects | No capable handler found | | Example | Amount + KYC + limit checks | Clerk → Officer → Manager |
How do you add a new step?
Adding a fraud-score check is a new handler inserted into the chain. No existing handler is modified.
public class FraudScoreValidator extends ValidationHandler {
@Override
protected void check(TransferRequest request) {
int score = fraudService.getScore(request.getFromId());
if (score < 30) {
throw new SecurityException(
"Fraud score too low: " + score);
}
}
}
// Insert into existing chain
kyc.setNext(new FraudScoreValidator());
// The new handler's next is set to the limit validator
How do you build the chain dynamically?
The chain can be built from configuration so different branches get different rules.
package com.bank.config;
import com.bank.chain.ValidationHandler;
import java.util.List;
public class ChainBuilder {
public static ValidationHandler build(List<ValidationHandler> handlers) {
for (int i = 0; i < handlers.size() - 1; i++) {
handlers.get(i).setNext(handlers.get(i + 1));
}
return handlers.get(0);
}
}
List<ValidationHandler> handlers = List.of(
new AmountValidator(),
new KycValidator(),
new FraudScoreValidator(),
new DailyLimitValidator()
);
ValidationHandler chain = ChainBuilder.build(handlers);
chain.validate(request);
What are common mistakes?
The first mistake is not handling the end of the chain. If no handler can process the request, it should be explicitly rejected, not silently dropped.
The second mistake is putting multiple responsibilities in one handler, which defeats the purpose of the chain.
// Wrong: handler checks amount AND KYC (two responsibilities)
public class BigValidator extends ValidationHandler {
protected void check(TransferRequest r) {
if (r.getAmount().compareTo(BigDecimal.ZERO) <= 0) { ... }
if (!r.isKycComplete()) { ... } // should be separate handler
}
}
What should a senior engineer know?
At National Bank of India, Pankaj builds transfer validation and loan approval using Chain of Responsibility. A regulated money operation must pass a precise, auditable sequence of independent checks, and the chain lets the team compose and evolve that sequence safely. Each handler is trivially testable in isolation because it does one thing.
Visual Overview
Validation Pipeline
───────────────────
Request ──→ AmountValidator ──→ KycValidator ──→ LimitValidator
(positive?) (KYC done?) (within limit?)
All must pass for the request to proceed.
Approval Workflow
─────────────────
Request (INR 3,00,000)
│
▼
Clerk (limit: 50K) ──→ cannot handle
│
▼
Officer (limit: 5L) ──→ APPROVED
│
(Manager never reached)
Key Takeaway
The Chain of Responsibility passes a request along a line of handlers, each deciding whether to process it or forward it to the next. This decouples the sender from the receivers and turns a monolithic validator or a giant if-else into a set of small, independent handlers that can be reordered, enabled, disabled, or extended freely.
The pattern suits two classic jobs. A validation pipeline runs every handler in sequence: each checks one rule, and all must pass. An approval workflow has each handler check its authority, and the first capable handler processes and stops. Adding a new step is a new handler class inserted into the chain with zero changes to existing handlers, which is the Open/Closed principle.
The big design advantage is that each handler is trivially testable in isolation because it does one thing, and the chain can be built dynamically from configuration so different branches or times of day get different rules. The critical safety rule is that a request that no handler accepts must have an explicit default outcome rather than silently vanishing.
At National Bank of India, Pankaj builds transfer validation and loan approval using Chain of Responsibility. A regulated money operation must pass a precise, auditable sequence of independent checks, and the chain lets the team compose and evolve that sequence safely.
Remember one sentence: Chain of Responsibility passes a request through independent handlers, decoupling the sender from the receivers and making the processing pipeline freely extensible.
---