The Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.
The Problem
Imagine you are building an Online Support System for your website. A request from a user might be a simple password reset, a technical bug report, or a high-level billing dispute.
If you put all the logic into one class, you get a giant, messy "switch" or "if-else" statement that checks the type and priority of every request. Every time you add a new department (e.g., "Legal"), you have to rewrite that core logic.
The Solution: The Chain
You create separate handler classes for each type of request. Each handler has a field for storing a reference to the next handler in the chain. If a handler can't solve the problem, it simply says, "Not my job," and passes it down the line.
Step-by-Step Java Implementation
1. The Handler Interface
This defines the method for handling requests and setting the next handler.
// SupportHandler.java
public abstract class SupportHandler {
protected SupportHandler nextHandler;
public void setNextHandler(SupportHandler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(String requestType, String message);
}
2. Concrete Handlers
Each class handles a specific "level" of the problem.
// BasicSupport.java
class BasicSupport extends SupportHandler {
public void handleRequest(String requestType, String message) {
if (requestType.equalsIgnoreCase("BASIC")) {
System.out.println("[Basic Support] Handling: " + message);
} else if (nextHandler != null) {
nextHandler.handleRequest(requestType, message);
}
}
}
// BillingSupport.java
class BillingSupport extends SupportHandler {
public void handleRequest(String requestType, String message) {
if (requestType.equalsIgnoreCase("BILLING")) {
System.out.println("[Billing Dept] Handling: " + message);
} else if (nextHandler != null) {
nextHandler.handleRequest(requestType, message);
}
}
}
Full Code for Testing
// Save as ChainTest.java
abstract class Logger {
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;
protected int level;
protected Logger nextLogger;
public void setNextLogger(Logger nextLogger) {
this.nextLogger = nextLogger;
}
public void logMessage(int level, String message) {
if (this.level <= level) {
write(message);
}
if (nextLogger != null) {
nextLogger.logMessage(level, message);
}
}
abstract protected void write(String message);
}
class ConsoleLogger extends Logger {
public ConsoleLogger(int level) { this.level = level; }
@Override
protected void write(String message) {
System.out.println("Standard Console::Logger: " + message);
}
}
class ErrorLogger extends Logger {
public ErrorLogger(int level) { this.level = level; }
@Override
protected void write(String message) {
System.err.println("Error Console::Logger: " + message);
}
}
class FileLogger extends Logger {
public FileLogger(int level) { this.level = level; }
@Override
protected void write(String message) {
System.out.println("File::Logger: " + message);
}
}
public class ChainTest {
private static Logger getChainOfLoggers() {
Logger errorLogger = new ErrorLogger(Logger.ERROR);
Logger fileLogger = new FileLogger(Logger.DEBUG);
Logger consoleLogger = new ConsoleLogger(Logger.INFO);
// Linking them: Error -> File -> Console
errorLogger.setNextLogger(fileLogger);
fileLogger.setNextLogger(consoleLogger);
return errorLogger;
}
public static void main(String[] args) {
Logger loggerChain = getChainOfLoggers();
System.out.println("--- Test 1: Info Level ---");
loggerChain.logMessage(Logger.INFO, "This is an information.");
System.out.println("\n--- Test 2: Debug Level ---");
loggerChain.logMessage(Logger.DEBUG, "This is a debug level information.");
System.out.println("\n--- Test 3: Error Level ---");
loggerChain.logMessage(Logger.ERROR, "This is an error information.");
}
}
Why use Chain of Responsibility?
- Decoupling: The sender of a request doesn't need to know which specific object will handle it.
- Flexibility: You can add, remove, or reorder handlers in the chain at runtime.
- Single Responsibility: Each handler only cares about its specific logic.
Real-World Example
- Java Exception Handling: When an exception is thrown, the JVM looks for a
catchblock in the current method. If not found, it passes the exception up the call stack to the calling method, and so on. - Middleware in Web Servers: In frameworks like Spring or Express.js, a request passes through a chain of filters (Auth, Logging, Compression) before hitting the final controller.