Introduction
The Chain of Responsibility (CoR) Pattern is a behavioral design pattern that allows a request to be passed along a chain of handlers until one of them handles it. This pattern decouples the sender and receiver, making systems more flexible and modular.
Why Chain of Responsibility Matters
When dealing with request processing, validation, or event handling, a system should not have one hardcoded handler. Instead, it should allow dynamic, reusable, and ordered handlers—this is what CoR provides.
Core Intent and Participants
- Intent: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.
Participants
Handler
: Declares an interface for handling requests and linking to the next handler.ConcreteHandler
: Handles requests it is responsible for or forwards to the next handler.Client
: Sends requests into the chain.
UML Diagram (Text)
+--------+ +----------------+ +----------------+
| Client | --> | ConcreteHandler| --> | ConcreteHandler| --> ...
+--------+ +----------------+ +----------------+
^
|
+-------------+
| Handler |
+-------------+
| + setNext() |
| + handle() |
+-------------+
Real-World Use Cases
- Servlet filters and middleware pipelines
- Logging frameworks (log level handlers)
- Request validation chains
- Event bubbling in GUIs
- Customer support ticket routing
Java Implementation Strategy
Example: Support Ticket System
Step 1: Handler Interface
public abstract class SupportHandler {
protected SupportHandler next;
public void setNext(SupportHandler next) {
this.next = next;
}
public abstract void handleRequest(String issueType);
}
Step 2: Concrete Handlers
public class LevelOneSupport extends SupportHandler {
public void handleRequest(String issueType) {
if (issueType.equalsIgnoreCase("basic")) {
System.out.println("Level 1 Support handled basic issue.");
} else if (next != null) {
next.handleRequest(issueType);
}
}
}
public class LevelTwoSupport extends SupportHandler {
public void handleRequest(String issueType) {
if (issueType.equalsIgnoreCase("intermediate")) {
System.out.println("Level 2 Support handled intermediate issue.");
} else if (next != null) {
next.handleRequest(issueType);
}
}
}
public class LevelThreeSupport extends SupportHandler {
public void handleRequest(String issueType) {
if (issueType.equalsIgnoreCase("advanced")) {
System.out.println("Level 3 Support handled advanced issue.");
} else {
System.out.println("No support level available for this issue.");
}
}
}
Step 3: Client Code
public class ChainDemo {
public static void main(String[] args) {
SupportHandler level1 = new LevelOneSupport();
SupportHandler level2 = new LevelTwoSupport();
SupportHandler level3 = new LevelThreeSupport();
level1.setNext(level2);
level2.setNext(level3);
level1.handleRequest("basic");
level1.handleRequest("intermediate");
level1.handleRequest("advanced");
level1.handleRequest("unknown");
}
}
✅ Output shows how the request is passed along the chain.
Pros and Cons
✅ Pros
- Reduces coupling between sender and receivers
- Flexible order and addition of handlers
- Promotes reusability of handler logic
❌ Cons
- Can be hard to debug due to dynamic routing
- Request may not be handled if no handler accepts it
- Increased complexity with long chains
Anti-Patterns and Misuse
- Overcomplicating simple linear logic with unnecessary chains
- Catch-all handler that defeats the purpose of the pattern
- Lack of proper fallback can lead to unhandled requests
Chain of Responsibility vs Strategy vs Observer
Pattern | Purpose | Key Concept | Client Aware of Receiver? |
---|---|---|---|
Chain of Responsibility | Decouple sender/receiver in order | Request routing | ❌ No |
Strategy | Select behavior dynamically | Behavior switching | ✅ Yes |
Observer | Notify all interested subscribers | Event notification | ❌ No |
Refactoring Legacy Code
Before
if (type.equals("basic")) { ... }
else if (type.equals("intermediate")) { ... }
else if (type.equals("advanced")) { ... }
After (Using Chain of Responsibility)
level1.handleRequest(type);
✅ Cleaner, testable, and extensible code.
Best Practices
- Keep handlers single-responsibility focused
- Clearly define when a handler should process or pass the request
- Avoid long unmaintainable chains—group or categorize them
- Use enums or request objects instead of raw strings
Real-World Analogy
Think of customer support tiers. A basic issue is resolved by the front desk. If they can’t help, it goes to a technician, and then to engineering. Each level passes the request only if it cannot handle it—that’s the Chain of Responsibility.
Java Version Relevance
- Java 8+: Use lambdas and functional handlers in dynamic chains
- Java 17+: Use records to define immutable request objects
- Spring: Filters and interceptors are built on this principle
Conclusion & Key Takeaways
- Chain of Responsibility decouples sender and receivers for clean request handling.
- Ideal for logging, validation, authorization, and dynamic event processing.
- Make each handler focused and easily swappable.
- Use it when requests must be processed conditionally across a pipeline.
FAQ – Chain of Responsibility Pattern in Java
1. What is the Chain of Responsibility Pattern?
A behavioral design pattern that allows passing a request along a chain of handlers.
2. What problems does it solve?
Decouples sender from receiver, promotes flexibility in request processing.
3. Where is it used in real life?
Logging, UI event bubbling, validation frameworks, servlet filters.
4. Is it the same as Strategy?
No. Strategy chooses behavior; CoR passes request across handlers.
5. What if no handler can process the request?
You can log a default message or throw an exception.
6. Can I break the chain early?
Yes. If a handler processes the request, it can stop propagation.
7. Is this pattern used in Spring?
Yes. Filters, interceptors, and handler mappings follow CoR.
8. Can this be combined with the Composite Pattern?
Yes. A tree-based command structure can use both.
9. How do I test handlers?
Mock the next handler and test each handler in isolation.
10. Is it good for REST APIs?
Yes, especially for authorization, validation, and request pre-processing.