Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
292: The Chain of Responsibility Pattern
I've seen this pattern play out in almost every mid-sized enterprise project I've touched: a developer starts with a simple piece of logic to handle a request, and six months later, they're staring at a 500-line method filled with nested if-else blocks. It usually happens when you're building something like a request processing pipeline—think of a system that needs to authenticate a user, check their permissions, validate the input, and then finally log the action.
The "Giant If-Else" Trap
When you first build a request pipeline, the naive approach is just to put everything in one place. You create a RequestProcessor class and a single method that handles the logic. It looks something like this:
public void processRequest(Request request) {
if (request.isMissingAuthToken()) {
throw new AuthException("No token provided");
}
if (!authService.isValid(request.getToken())) {
throw new AuthException("Invalid token");
}
if (rateLimiter.isExceeded(request.getIp())) {
throw new RateLimitException("Too many requests");
}
if (!validator.isValid(request.getBody())) {
throw new ValidationException("Bad payload");
}
// Finally, do the actual work...
doWork(request);
}
At first glance, this is actually quite readable. It's linear. You can see exactly what happens and in what order. But here is where the rot sets in. What happens when the product manager asks you to add a "Maintenance Mode" check? Or a "Premium User" bypass for the rate limiter? Or a specialized validation step for only certain types of requests?
Suddenly, your processRequest method is an absolute monster. You're modifying the same file every single time a new requirement drops, which is a nightmare for version control and a violation of the Open/Closed Principle. You're forcing one class to know everything about authentication, rate limiting, and validation. That's a recipe for a "God Class" that everyone is afraid to touch.
Decoupling with the Chain of Responsibility
The better way—and the core of the Chain of Responsibility pattern—is to stop thinking of this as one big process and start thinking of it as a series of independent "handlers." Each handler has one job: either process the request or pass it to the next person in line.
I like to think of it like a corporate approval process. You send a request to your manager; if they can't approve it, they pass it to the director; if the director can't, it goes to the VP. You don't need to know who the VP is; you just need to know who your manager is.
In Java, we implement this by defining a common interface for our handlers and giving each handler a reference to the next one in the chain:
public abstract class Handler {
protected Handler next;
public void setNext(Handler next) {
this.next = next;
}
public abstract void handle(Request request);
protected void passToNext(Request request) {
if (next != null) {
next.handle(request);
}
}
}
Now, your specific logic is encapsulated in small, focused classes. An AuthHandler only cares about tokens. A RateLimitHandler only cares about IPs. If the AuthHandler sees the token is valid, it simply calls passToNext(request). If it sees the token is missing, it throws the exception and the chain stops immediately.
Trading Control for Flexibility
Now, I'll be honest with you: this approach has a cost. You've traded a simple, linear method for a more complex object graph. If you're debugging a request and you don't know how the chain was assembled, it can feel like you're chasing a ghost through your codebase. You can't just "jump to definition" to see the whole flow; you have to figure out which handlers were linked together at runtime.
However, the trade-off is almost always worth it as the project grows. You can now reorder the chain, add new handlers, or remove old ones without touching the code of the other handlers. You can even build different chains for different environments—for example, a "DevelopmentChain" that skips the rate limiter but keeps the authentication. You've moved the configuration of the process away from the implementation of the process, and that is where the real power lies.
📋 Practical Task
Implementing a Multi-Tier Customer Support Router
You are building a customer support ticket routing system. Tickets come in with a "severity" level (LOW, MEDIUM, HIGH). Depending on the severity, different tiers of support should handle the ticket. If a tier cannot handle the ticket (because it's too severe), it should be passed to the next tier.
Your Task:
- Create a
Ticketclass with aseverityfield (using an Enum). - Implement a
SupportHandlerabstract class that follows the Chain of Responsibility pattern. - Create three concrete handlers:
JuniorSupport(handles LOW),SeniorSupport(handles MEDIUM), andManagerSupport(handles HIGH). - If a ticket is passed to a handler that cannot handle its severity, the handler should pass it to the next person in the chain.
- If a ticket reaches the end of the chain without being handled, print a message stating the ticket could not be processed.
- In your
Mainclass, assemble the chain (Junior → Senior → Manager) and test it with tickets of varying severities.
There are no comments for now.