Chain of Responsibility Design Pattern in Java
Learn Chain of Responsibility Design Pattern in Java with request processing pipelines, validation chains, approval workflows, Spring Security filter chain, diagrams, code examples, benefits, limitations, and interview questions.
What You Will Learn
In this article, you'll learn:
- What is Chain of Responsibility Pattern?
- Why Chain of Responsibility is needed
- Real-world examples
- Request processing pipelines
- Java implementation
- Approval workflow example
- Spring Security filter chain
- Servlet filter chain
- Benefits and limitations
- Interview questions
Introduction
In enterprise applications, a request often goes through multiple steps before completion.
Example:
HTTP Request
→ Authentication
→ Authorization
→ Validation
→ Logging
→ Business Logic
Another example:
Loan Application
→ Basic Validation
→ Credit Score Check
→ Income Verification
→ Manager Approval
→ Final Approval
If all logic is written in one class, the code becomes complex and hard to maintain.
Chain of Responsibility solves this problem by passing a request through a chain of handlers.
What is Chain of Responsibility Pattern?
Chain of Responsibility is a Behavioral Design Pattern that allows a request to pass through a chain of handlers.
Each handler decides:
Can I handle this request?
Yes → Handle it
No → Pass to next handler
Purpose of Chain of Responsibility
The main purpose is:
Avoid tightly coupling sender and receiver.
Instead of one object handling everything, multiple handlers are connected in a chain.
Real World Analogy
Think about customer support.
Customer Issue
→ Level 1 Support
→ Level 2 Support
→ Manager
→ Engineering Team
Each level checks if it can resolve the issue.
If not, it forwards the issue to the next level.
This is Chain of Responsibility.
Problem Without Chain of Responsibility
public class LoanApprovalService {
public void approveLoan(LoanApplication application) {
if (application.getAmount() <= 0) {
throw new RuntimeException("Invalid loan amount");
}
if (application.getCreditScore() < 700) {
throw new RuntimeException("Low credit score");
}
if (application.getIncome() < 50000) {
throw new RuntimeException("Low income");
}
System.out.println("Loan Approved");
}
}
Problems in This Code
- Too many conditions in one class
- Hard to add new validation
- Violates Single Responsibility Principle
- Difficult to test each step independently
- Business logic and validation logic are mixed
Solution With Chain of Responsibility
flowchart LR
A[Loan Application] --> B[Amount Validator]
B --> C[Credit Score Validator]
C --> D[Income Validator]
D --> E[Approval Handler]
Each handler has one responsibility.
Chain of Responsibility Structure
classDiagram
class Handler {
<<abstract>>
- nextHandler
+ setNext(handler)
+ handle(request)
}
class AmountValidator
class CreditScoreValidator
class IncomeValidator
class ApprovalHandler
Handler <|-- AmountValidator
Handler <|-- CreditScoreValidator
Handler <|-- IncomeValidator
Handler <|-- ApprovalHandler
Handler --> Handler
Key Components
Handler
Common abstract class or interface.
It defines:
handle()
setNext()
Concrete Handler
Actual processing step.
Examples:
Amount Validator
Credit Score Validator
Income Validator
Approval Handler
Client
Builds the chain and sends the request.
Loan Application Example
Step 1: Create Request Object
public class LoanApplication {
private double amount;
private int creditScore;
private double income;
public LoanApplication(
double amount,
int creditScore,
double income) {
this.amount = amount;
this.creditScore = creditScore;
this.income = income;
}
public double getAmount() {
return amount;
}
public int getCreditScore() {
return creditScore;
}
public double getIncome() {
return income;
}
}
Step 2: Create Abstract Handler
public abstract class LoanHandler {
private LoanHandler nextHandler;
public LoanHandler setNext(
LoanHandler nextHandler) {
this.nextHandler = nextHandler;
return nextHandler;
}
public void handle(
LoanApplication application) {
process(application);
if (nextHandler != null) {
nextHandler.handle(application);
}
}
protected abstract void process(
LoanApplication application);
}
Step 3: Amount Validator
public class AmountValidator
extends LoanHandler {
@Override
protected void process(
LoanApplication application) {
if (application.getAmount() <= 0) {
throw new RuntimeException(
"Loan amount must be greater than zero");
}
System.out.println("Amount validation passed");
}
}
Step 4: Credit Score Validator
public class CreditScoreValidator
extends LoanHandler {
@Override
protected void process(
LoanApplication application) {
if (application.getCreditScore() < 700) {
throw new RuntimeException(
"Credit score is too low");
}
System.out.println("Credit score validation passed");
}
}
Step 5: Income Validator
public class IncomeValidator
extends LoanHandler {
@Override
protected void process(
LoanApplication application) {
if (application.getIncome() < 50000) {
throw new RuntimeException(
"Income is too low");
}
System.out.println("Income validation passed");
}
}
Step 6: Approval Handler
public class ApprovalHandler
extends LoanHandler {
@Override
protected void process(
LoanApplication application) {
System.out.println("Loan Approved Successfully");
}
}
Step 7: Client Code
public class ChainOfResponsibilityDemo {
public static void main(String[] args) {
LoanApplication application =
new LoanApplication(
250000,
760,
90000);
LoanHandler amountValidator =
new AmountValidator();
LoanHandler creditScoreValidator =
new CreditScoreValidator();
LoanHandler incomeValidator =
new IncomeValidator();
LoanHandler approvalHandler =
new ApprovalHandler();
amountValidator
.setNext(creditScoreValidator)
.setNext(incomeValidator)
.setNext(approvalHandler);
amountValidator.handle(application);
}
}
Output
Amount validation passed
Credit score validation passed
Income validation passed
Loan Approved Successfully
Execution Flow
sequenceDiagram
participant Client
participant Amount
participant Credit
participant Income
participant Approval
Client->>Amount: handle(application)
Amount->>Credit: handle(application)
Credit->>Income: handle(application)
Income->>Approval: handle(application)
Approval-->>Client: Approved
Failed Request Example
Input:
LoanApplication application =
new LoanApplication(
250000,
620,
90000);
Output:
Amount validation passed
Exception: Credit score is too low
The request stops when one handler fails.
Real Enterprise Example: Approval Workflow
Expense Approval:
Expense Request
→ Team Lead
→ Manager
→ Director
→ Finance
Each approver handles based on amount.
Expense Approval Flow
flowchart LR
A[Expense Request] --> B[Team Lead]
B --> C[Manager]
C --> D[Director]
D --> E[Finance]
Banking Use Case
Transaction Processing:
Transaction Request
→ Authentication Check
→ Balance Check
→ Fraud Check
→ Limit Check
→ Transfer Execution
Banking Flow
flowchart LR
A[Transaction] --> B[Authentication]
B --> C[Balance Check]
C --> D[Fraud Check]
D --> E[Limit Check]
E --> F[Transfer]
Insurance Use Case
Claim Processing:
Claim Request
→ Policy Validation
→ Document Verification
→ Fraud Check
→ Medical Review
→ Claim Approval
Insurance Flow
flowchart LR
A[Claim] --> B[Policy Validation]
B --> C[Document Check]
C --> D[Fraud Check]
D --> E[Medical Review]
E --> F[Approval]
Spring Security Example
Spring Security uses filter chains.
Request flow:
HTTP Request
→ Security Filters
→ Controller
Spring Security Filter Chain
flowchart LR
A[HTTP Request] --> B[Authentication Filter]
B --> C[Authorization Filter]
C --> D[CSRF Filter]
D --> E[Controller]
Each filter processes the request and forwards it to the next filter.
Servlet Filter Chain Example
public class LoggingFilter implements Filter {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
System.out.println("Request received");
chain.doFilter(request, response);
System.out.println("Response sent");
}
}
Chain Flow in Servlet
Filter 1
→ Filter 2
→ Filter 3
→ Servlet
This is a classic Chain of Responsibility example.
API Gateway Example
API Gateway request pipeline:
Request
→ Authentication
→ Authorization
→ Rate Limiting
→ Logging
→ Routing
→ Microservice
API Gateway Flow
flowchart LR
A[Request] --> B[Authentication]
B --> C[Authorization]
C --> D[Rate Limiting]
D --> E[Logging]
E --> F[Routing]
F --> G[Microservice]
Framework Examples
| Framework | Chain Example |
|---|---|
| Spring Security | Security Filter Chain |
| Servlet API | FilterChain |
| Spring MVC | Handler Interceptors |
| Apache Camel | Route Processing |
| Netty | Channel Pipeline |
| API Gateway | Middleware Chain |
Benefits
✅ Reduces coupling between sender and receiver
✅ Improves code maintainability
✅ Each handler has one responsibility
✅ Easy to add or remove steps
✅ Good for validation pipelines
✅ Good for workflow processing
✅ Supports Open Closed Principle
Limitations
❌ Request may go unhandled
❌ Debugging can be harder
❌ Chain order matters
❌ Too many handlers can increase complexity
When To Use
Use Chain of Responsibility when:
- Multiple handlers may process a request
- Request processing has ordered steps
- You need validation pipelines
- You want loosely coupled handlers
- You need flexible workflow processing
When Not To Use
Avoid this pattern when:
- Only one object handles the request
- Processing order is fixed and simple
- Chain adds unnecessary complexity
Chain of Responsibility vs Decorator
| Feature | Chain of Responsibility | Decorator |
|---|---|---|
| Goal | Pass request through handlers | Add behavior to object |
| Flow | Handler to handler | Wrapper to wrapped object |
| Request Handling | May stop anytime | Usually all decorators execute |
| Example | Security Filters | Java IO Streams |
Chain of Responsibility vs Command
| Feature | Chain of Responsibility | Command |
|---|---|---|
| Purpose | Request processing pipeline | Encapsulate request as object |
| Focus | Handler chain | Action object |
| Example | Approval workflow | Undo/Redo operations |
Interview Questions
What is Chain of Responsibility Pattern?
A behavioral pattern where a request passes through a chain of handlers until it is handled.
Real World Example?
Customer support escalation or loan approval workflow.
Where is it used in Java?
Servlet FilterChain and Spring Security Filter Chain.
What problem does it solve?
It avoids coupling sender and receiver and separates request processing steps.
Can a request stop in the middle?
Yes. A handler can stop processing if validation fails or if it fully handles the request.
Why is order important?
Because each handler may depend on the result of previous handlers.
Key Takeaways
- Chain of Responsibility is a Behavioral Design Pattern.
- It passes requests through a chain of handlers.
- Each handler has one responsibility.
- Handlers can process, forward, or stop the request.
- Commonly used in validation, security, approval workflows, and middleware pipelines.
- Spring Security and Servlet FilterChain are classic real-world examples.
- It improves flexibility and reduces tight coupling.