Chain of Responsibility Pattern in Java

Introduction

The Chain of Responsibility Pattern is a behavioral design pattern that allows passing 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.

Core Concept

Definition

Chain of Responsibility Pattern lets you pass requests along a chain of handlers. Each handler decides either to process the request or to pass it to the next handler in the chain.

When to Use

  • When you want to give more than one object a chance to handle a request
  • When you want to decouple the sender and receiver of a request
  • When multiple objects can handle a request, but the handler isn't known beforehand
  • When you want to dynamically specify handlers at runtime

Basic Implementation

1. Handler Interface

// Handler interface
public interface Handler {
void setNext(Handler handler);
void handle(Request request);
}
// Request class
public class Request {
private String type;
private String content;
private int severity; // 1-10, where 10 is most severe
public Request(String type, String content, int severity) {
this.type = type;
this.content = content;
this.severity = severity;
}
// Getters and setters
public String getType() { return type; }
public String getContent() { return content; }
public int getSeverity() { return severity; }
public void setType(String type) { this.type = type; }
public void setContent(String content) { this.content = content; }
public void setSeverity(int severity) { this.severity = severity; }
@Override
public String toString() {
return String.format("Request{type='%s', content='%s', severity=%d}", 
type, content, severity);
}
}

2. Abstract Base Handler

// Abstract base handler
public abstract class AbstractHandler implements Handler {
private Handler nextHandler;
@Override
public void setNext(Handler handler) {
this.nextHandler = handler;
}
@Override
public void handle(Request request) {
if (nextHandler != null) {
nextHandler.handle(request);
} else {
// End of chain - no handler could process the request
System.out.println("No handler available for: " + request);
}
}
// Template method pattern - subclasses implement canHandle and process
protected abstract boolean canHandle(Request request);
protected abstract void process(Request request);
}

3. Concrete Handlers

// Concrete handler for low severity requests
public class LowSeverityHandler extends AbstractHandler {
@Override
protected boolean canHandle(Request request) {
return request.getSeverity() <= 3;
}
@Override
protected void process(Request request) {
System.out.println("LowSeverityHandler processing: " + request.getContent());
System.out.println("-> Assigned to Junior Support");
}
@Override
public void handle(Request request) {
if (canHandle(request)) {
process(request);
} else {
super.handle(request); // Pass to next handler
}
}
}
// Concrete handler for medium severity requests
public class MediumSeverityHandler extends AbstractHandler {
@Override
protected boolean canHandle(Request request) {
return request.getSeverity() <= 7;
}
@Override
protected void process(Request request) {
System.out.println("MediumSeverityHandler processing: " + request.getContent());
System.out.println("-> Assigned to Senior Support");
}
@Override
public void handle(Request request) {
if (canHandle(request)) {
process(request);
} else {
super.handle(request);
}
}
}
// Concrete handler for high severity requests
public class HighSeverityHandler extends AbstractHandler {
@Override
protected boolean canHandle(Request request) {
return request.getSeverity() <= 10;
}
@Override
protected void process(Request request) {
System.out.println("HighSeverityHandler processing: " + request.getContent());
System.out.println("-> Escalated to Management");
}
@Override
public void handle(Request request) {
if (canHandle(request)) {
process(request);
} else {
super.handle(request);
}
}
}

4. Client Code

// Client code
public class ChainOfResponsibilityDemo {
public static void main(String[] args) {
// Create handlers
Handler lowHandler = new LowSeverityHandler();
Handler mediumHandler = new MediumSeverityHandler();
Handler highHandler = new HighSeverityHandler();
// Build the chain
lowHandler.setNext(mediumHandler);
mediumHandler.setNext(highHandler);
// Create requests with different severity levels
Request[] requests = {
new Request("BUG", "Button color is wrong", 2),
new Request("FEATURE", "Add new payment method", 5),
new Request("CRITICAL", "System crash on startup", 9),
new Request("SECURITY", "Data breach detected", 10)
};
// Process requests
System.out.println("Processing requests through chain:\n");
for (Request request : requests) {
System.out.println("=== Processing: " + request + " ===");
lowHandler.handle(request);
System.out.println();
}
}
}

Advanced Implementation Variations

1. Handler with Multiple Conditions

// Advanced handler with multiple conditions
public class TechnicalSupportHandler extends AbstractHandler {
private List<String> supportedTypes = Arrays.asList("BUG", "TECHNICAL", "PERFORMANCE");
@Override
protected boolean canHandle(Request request) {
return supportedTypes.contains(request.getType()) && 
request.getSeverity() <= 6;
}
@Override
protected void process(Request request) {
System.out.println("TechnicalSupportHandler processing: " + request.getContent());
// Different processing based on type
switch (request.getType()) {
case "BUG":
System.out.println("-> Creating bug ticket in JIRA");
break;
case "TECHNICAL":
System.out.println("-> Assigning to technical team");
break;
case "PERFORMANCE":
System.out.println("-> Running performance analysis");
break;
}
}
@Override
public void handle(Request request) {
if (canHandle(request)) {
process(request);
} else {
super.handle(request);
}
}
}
public class BillingSupportHandler extends AbstractHandler {
@Override
protected boolean canHandle(Request request) {
return "BILLING".equals(request.getType()) || 
"PAYMENT".equals(request.getType());
}
@Override
protected void process(Request request) {
System.out.println("BillingSupportHandler processing: " + request.getContent());
System.out.println("-> Forwarding to finance department");
}
@Override
public void handle(Request request) {
if (canHandle(request)) {
process(request);
} else {
super.handle(request);
}
}
}

2. Chain Builder for Flexible Configuration

// Chain builder for easy chain configuration
public class HandlerChainBuilder {
private Handler firstHandler;
private Handler lastHandler;
public HandlerChainBuilder addHandler(Handler handler) {
if (firstHandler == null) {
firstHandler = handler;
lastHandler = handler;
} else {
lastHandler.setNext(handler);
lastHandler = handler;
}
return this;
}
public Handler build() {
return firstHandler;
}
}
// Usage
public class AdvancedChainDemo {
public static void main(String[] args) {
// Build chain using builder
Handler chain = new HandlerChainBuilder()
.addHandler(new TechnicalSupportHandler())
.addHandler(new BillingSupportHandler())
.addHandler(new LowSeverityHandler())
.addHandler(new MediumSeverityHandler())
.addHandler(new HighSeverityHandler())
.build();
// Test various requests
Request[] requests = {
new Request("BUG", "Login page not loading", 3),
new Request("BILLING", "Invoice discrepancy", 4),
new Request("FEATURE", "New dashboard design", 2),
new Request("TECHNICAL", "Database connection issues", 7)
};
System.out.println("Processing mixed requests:\n");
for (Request request : requests) {
System.out.println("=== Processing: " + request + " ===");
chain.handle(request);
System.out.println();
}
}
}

Real-World Examples

1. Logging Framework

// Logging level enum
public enum LogLevel {
DEBUG(1), INFO(2), WARN(3), ERROR(4), FATAL(5);
private final int level;
LogLevel(int level) {
this.level = level;
}
public int getLevel() {
return level;
}
}
// Log message
public class LogMessage {
private LogLevel level;
private String message;
private String loggerName;
private long timestamp;
public LogMessage(LogLevel level, String message, String loggerName) {
this.level = level;
this.message = message;
this.loggerName = loggerName;
this.timestamp = System.currentTimeMillis();
}
// Getters
public LogLevel getLevel() { return level; }
public String getMessage() { return message; }
public String getLoggerName() { return loggerName; }
public long getTimestamp() { return timestamp; }
}
// Logger handler
public abstract class LoggerHandler {
private LoggerHandler next;
protected LogLevel level;
public LoggerHandler(LogLevel level) {
this.level = level;
}
public void setNext(LoggerHandler next) {
this.next = next;
}
public void log(LogMessage message) {
if (message.getLevel().getLevel() >= this.level.getLevel()) {
writeMessage(message);
}
if (next != null) {
next.log(message);
}
}
protected abstract void writeMessage(LogMessage message);
}
// Concrete loggers
public class ConsoleLogger extends LoggerHandler {
public ConsoleLogger(LogLevel level) {
super(level);
}
@Override
protected void writeMessage(LogMessage message) {
System.out.printf("[CONSOLE] %s - %s: %s%n", 
message.getLevel(), 
message.getLoggerName(), 
message.getMessage());
}
}
public class FileLogger extends LoggerHandler {
private String filename;
public FileLogger(LogLevel level, String filename) {
super(level);
this.filename = filename;
}
@Override
protected void writeMessage(LogMessage message) {
// Simulate file writing
System.out.printf("[FILE:%s] %s - %s: %s%n", 
filename,
message.getLevel(), 
message.getLoggerName(), 
message.getMessage());
}
}
public class DatabaseLogger extends LoggerHandler {
public DatabaseLogger(LogLevel level) {
super(level);
}
@Override
protected void writeMessage(LogMessage message) {
// Simulate database insertion
System.out.printf("[DATABASE] %s - %s: %s%n", 
message.getLevel(), 
message.getLoggerName(), 
message.getMessage());
}
}
// Logging client
public class LoggingSystem {
private LoggerHandler loggerChain;
public LoggingSystem() {
setupLoggerChain();
}
private void setupLoggerChain() {
LoggerHandler consoleLogger = new ConsoleLogger(LogLevel.DEBUG);
LoggerHandler fileLogger = new FileLogger(LogLevel.INFO, "app.log");
LoggerHandler dbLogger = new DatabaseLogger(LogLevel.ERROR);
consoleLogger.setNext(fileLogger);
fileLogger.setNext(dbLogger);
loggerChain = consoleLogger;
}
public void log(LogLevel level, String loggerName, String message) {
LogMessage logMessage = new LogMessage(level, message, loggerName);
loggerChain.log(logMessage);
}
// Convenience methods
public void debug(String loggerName, String message) {
log(LogLevel.DEBUG, loggerName, message);
}
public void info(String loggerName, String message) {
log(LogLevel.INFO, loggerName, message);
}
public void error(String loggerName, String message) {
log(LogLevel.ERROR, loggerName, message);
}
}
// Demo
public class LoggingDemo {
public static void main(String[] args) {
LoggingSystem loggingSystem = new LoggingSystem();
System.out.println("Testing logging chain:\n");
loggingSystem.debug("UserService", "User login attempted");
loggingSystem.info("OrderService", "Order created successfully");
loggingSystem.error("PaymentService", "Payment gateway timeout");
loggingSystem.log(LogLevel.FATAL, "AuthService", "Security breach detected");
}
}

2. Authentication and Authorization Chain

// Authentication request
public class AuthRequest {
private String username;
private String password;
private String token;
private String resource;
private String action;
public AuthRequest(String username, String password, String token, 
String resource, String action) {
this.username = username;
this.password = password;
this.token = token;
this.resource = resource;
this.action = action;
}
// Getters
public String getUsername() { return username; }
public String getPassword() { return password; }
public String getToken() { return token; }
public String getResource() { return resource; }
public String getAction() { return action; }
}
// Authentication result
public class AuthResult {
private boolean authenticated;
private boolean authorized;
private String message;
private String userRole;
public AuthResult(boolean authenticated, boolean authorized, 
String message, String userRole) {
this.authenticated = authenticated;
this.authorized = authorized;
this.message = message;
this.userRole = userRole;
}
// Getters and setters
public boolean isAuthenticated() { return authenticated; }
public boolean isAuthorized() { return authorized; }
public String getMessage() { return message; }
public String getUserRole() { return userRole; }
public void setAuthenticated(boolean authenticated) { this.authenticated = authenticated; }
public void setAuthorized(boolean authorized) { this.authorized = authorized; }
public void setMessage(String message) { this.message = message; }
public void setUserRole(String userRole) { this.userRole = userRole; }
}
// Authentication handler
public abstract class AuthHandler {
private AuthHandler next;
public void setNext(AuthHandler next) {
this.next = next;
}
public AuthResult handle(AuthRequest request, AuthResult result) {
// Process current handler
process(request, result);
// Pass to next handler if exists and processing should continue
if (next != null && shouldContinue(request, result)) {
return next.handle(request, result);
}
return result;
}
protected abstract void process(AuthRequest request, AuthResult result);
protected abstract boolean shouldContinue(AuthRequest request, AuthResult result);
}
// Concrete authentication handlers
public class TokenValidationHandler extends AuthHandler {
@Override
protected void process(AuthRequest request, AuthResult result) {
if (request.getToken() != null && !request.getToken().isEmpty()) {
// Simulate token validation
if (isValidToken(request.getToken())) {
result.setAuthenticated(true);
result.setUserRole("API_USER");
result.setMessage("Token validation successful");
} else {
result.setMessage("Invalid token");
}
}
}
@Override
protected boolean shouldContinue(AuthRequest request, AuthResult result) {
// Continue only if token validation failed
return !result.isAuthenticated();
}
private boolean isValidToken(String token) {
// Simulate token validation logic
return token.startsWith("valid_");
}
}
public class CredentialValidationHandler extends AuthHandler {
@Override
protected void process(AuthRequest request, AuthResult result) {
if (!result.isAuthenticated() && 
request.getUsername() != null && request.getPassword() != null) {
// Simulate credential validation
if (isValidCredentials(request.getUsername(), request.getPassword())) {
result.setAuthenticated(true);
result.setUserRole(getUserRole(request.getUsername()));
result.setMessage("Credential validation successful");
} else {
result.setMessage("Invalid credentials");
}
}
}
@Override
protected boolean shouldContinue(AuthRequest request, AuthResult result) {
// Continue to authorization if authenticated
return result.isAuthenticated();
}
private boolean isValidCredentials(String username, String password) {
// Simulate credential check
return "admin".equals(username) && "password".equals(password) ||
"user".equals(username) && "123456".equals(password);
}
private String getUserRole(String username) {
return "admin".equals(username) ? "ADMIN" : "USER";
}
}
public class AuthorizationHandler extends AuthHandler {
private Map<String, List<String>> permissions = new HashMap<>();
public AuthorizationHandler() {
// Initialize permissions
permissions.put("ADMIN", Arrays.asList("READ", "WRITE", "DELETE", "EXECUTE"));
permissions.put("USER", Arrays.asList("READ", "WRITE"));
permissions.put("API_USER", Arrays.asList("READ"));
}
@Override
protected void process(AuthRequest request, AuthResult result) {
if (result.isAuthenticated()) {
String userRole = result.getUserRole();
String action = request.getAction();
if (hasPermission(userRole, action)) {
result.setAuthorized(true);
result.setMessage("Authorization granted for " + action);
} else {
result.setAuthorized(false);
result.setMessage("Insufficient permissions for " + action);
}
}
}
@Override
protected boolean shouldContinue(AuthRequest request, AuthResult result) {
// Stop chain after authorization
return false;
}
private boolean hasPermission(String role, String action) {
return permissions.containsKey(role) && 
permissions.get(role).contains(action.toUpperCase());
}
}
// Authentication system
public class AuthenticationSystem {
private AuthHandler authChain;
public AuthenticationSystem() {
setupAuthChain();
}
private void setupAuthChain() {
AuthHandler tokenHandler = new TokenValidationHandler();
AuthHandler credentialHandler = new CredentialValidationHandler();
AuthHandler authorizationHandler = new AuthorizationHandler();
tokenHandler.setNext(credentialHandler);
credentialHandler.setNext(authorizationHandler);
authChain = tokenHandler;
}
public AuthResult authenticate(AuthRequest request) {
AuthResult initialResult = new AuthResult(false, false, "Not authenticated", null);
return authChain.handle(request, initialResult);
}
}
// Demo
public class AuthDemo {
public static void main(String[] args) {
AuthenticationSystem authSystem = new AuthenticationSystem();
System.out.println("Testing authentication chain:\n");
// Test cases
AuthRequest[] requests = {
// Valid token, read access
new AuthRequest(null, null, "valid_token123", "users", "READ"),
// Admin credentials, full access
new AuthRequest("admin", "password", null, "system", "DELETE"),
// User credentials, limited access
new AuthRequest("user", "123456", null, "reports", "READ"),
// Invalid credentials
new AuthRequest("user", "wrongpass", null, "data", "READ"),
// No authentication
new AuthRequest(null, null, null, "public", "READ")
};
for (AuthRequest request : requests) {
System.out.println("=== Processing Auth Request ===");
System.out.println("Username: " + request.getUsername());
System.out.println("Resource: " + request.getResource());
System.out.println("Action: " + request.getAction());
AuthResult result = authSystem.authenticate(request);
System.out.println("Result:");
System.out.println("  Authenticated: " + result.isAuthenticated());
System.out.println("  Authorized: " + result.isAuthorized());
System.out.println("  Role: " + result.getUserRole());
System.out.println("  Message: " + result.getMessage());
System.out.println();
}
}
}

Advanced Patterns with Chain of Responsibility

1. Chain with Break Conditions

// Handler that can break the chain
public abstract class BreakableHandler extends AbstractHandler {
protected boolean shouldBreakChain(Request request) {
return false; // Default - don't break chain
}
@Override
public void handle(Request request) {
if (canHandle(request)) {
process(request);
// Check if chain should break after processing
if (shouldBreakChain(request)) {
return; // Break the chain
}
}
// Continue to next handler
super.handle(request);
}
}
// Emergency handler that breaks the chain
public class EmergencyHandler extends BreakableHandler {
@Override
protected boolean canHandle(Request request) {
return "EMERGENCY".equals(request.getType()) || 
request.getSeverity() == 10;
}
@Override
protected void process(Request request) {
System.out.println("🚨 EMERGENCY HANDLER ACTIVATED!");
System.out.println("Processing: " + request.getContent());
System.out.println("-> Immediate action required!");
}
@Override
protected boolean shouldBreakChain(Request request) {
// Always break chain for emergencies
return true;
}
}

2. Chain with Request Modification

// Handler that can modify the request
public abstract class ModifyingHandler extends AbstractHandler {
@Override
public void handle(Request request) {
// Pre-processing: modify request if needed
Request modifiedRequest = preProcess(request);
if (canHandle(modifiedRequest)) {
process(modifiedRequest);
} else {
// Pass modified request to next handler
if (nextHandler != null) {
nextHandler.handle(modifiedRequest);
}
}
}
protected Request preProcess(Request request) {
// Default implementation - no modification
return request;
}
}
// Handler that enriches request with additional data
public class RequestEnricher extends ModifyingHandler {
@Override
protected Request preProcess(Request request) {
// Add timestamp and request ID
String enrichedContent = String.format("[%s] %s", 
java.time.LocalDateTime.now(), request.getContent());
Request enriched = new Request(
request.getType(), 
enrichedContent, 
request.getSeverity()
);
return enriched;
}
@Override
protected boolean canHandle(Request request) {
// This handler doesn't actually process, just enriches
return false;
}
@Override
protected void process(Request request) {
// Not used since canHandle always returns false
}
}

3. Chain with Parallel Processing

// Handler that processes in parallel
public class ParallelHandler implements Handler {
private List<Handler> parallelHandlers = new ArrayList<>();
public void addHandler(Handler handler) {
parallelHandlers.add(handler);
}
@Override
public void setNext(Handler handler) {
// Not used in parallel handler
}
@Override
public void handle(Request request) {
// Process with all parallel handlers
List<Thread> threads = new ArrayList<>();
for (Handler handler : parallelHandlers) {
Thread thread = new Thread(() -> {
try {
handler.handle(request);
} catch (Exception e) {
System.err.println("Handler failed: " + e.getMessage());
}
});
threads.add(thread);
thread.start();
}
// Wait for all threads to complete
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
// Usage example
public class ParallelChainDemo {
public static void main(String[] args) {
ParallelHandler parallelHandler = new ParallelHandler();
parallelHandler.addHandler(new LowSeverityHandler());
parallelHandler.addHandler(new TechnicalSupportHandler());
parallelHandler.addHandler(new BillingSupportHandler());
Request request = new Request("MIXED", "Multiple aspects issue", 5);
System.out.println("Processing with parallel handlers:");
parallelHandler.handle(request);
}
}

Testing Chain of Responsibility

Unit Testing Handlers

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import static org.junit.jupiter.api.Assertions.*;
public class HandlerTest {
private Handler lowHandler;
private Handler mediumHandler;
private Handler highHandler;
@BeforeEach
void setUp() {
lowHandler = new LowSeverityHandler();
mediumHandler = new MediumSeverityHandler();
highHandler = new HighSeverityHandler();
lowHandler.setNext(mediumHandler);
mediumHandler.setNext(highHandler);
}
@Test
void testLowSeverityHandling() {
Request request = new Request("BUG", "Minor issue", 2);
// Capture output or use mocking framework in real scenario
assertDoesNotThrow(() -> lowHandler.handle(request));
}
@Test
void testMediumSeverityHandling() {
Request request = new Request("FEATURE", "Medium priority", 5);
assertDoesNotThrow(() -> lowHandler.handle(request));
}
@Test
void testHighSeverityHandling() {
Request request = new Request("CRITICAL", "System down", 9);
assertDoesNotThrow(() -> lowHandler.handle(request));
}
@Test
void testUnhandledRequest() {
Request request = new Request("UNKNOWN", "Unknown type", 11);
// Should not throw exception, but reach end of chain
assertDoesNotThrow(() -> lowHandler.handle(request));
}
@Test
void testChainOrder() {
// Test that handlers are called in correct order
MockHandler first = new MockHandler("First");
MockHandler second = new MockHandler("Second");
MockHandler third = new MockHandler("Third");
first.setNext(second);
second.setNext(third);
Request request = new Request("TEST", "Test request", 1);
first.handle(request);
// Verify all handlers were called
assertTrue(first.wasCalled());
assertTrue(second.wasCalled());
assertTrue(third.wasCalled());
}
}
// Mock handler for testing
class MockHandler implements Handler {
private String name;
private boolean called = false;
private Handler next;
public MockHandler(String name) {
this.name = name;
}
@Override
public void setNext(Handler handler) {
this.next = handler;
}
@Override
public void handle(Request request) {
this.called = true;
System.out.println(name + " handler called");
if (next != null) {
next.handle(request);
}
}
public boolean wasCalled() {
return called;
}
}

Best Practices and Considerations

1. When to Use Chain of Responsibility

// Good use cases:
// 1. Multiple objects may handle a request
public class PurchaseApproval {
// Different managers can approve different amounts
}
// 2. You want to decouple sender and receiver
public class EventSystem {
// Events can be handled by multiple subscribers
}
// 3. The handler isn't known in advance
public class PluginSystem {
// Plugins can register handlers dynamically
}
// 4. You need dynamic handler configuration
public class FilterChain {
// Filters can be added/removed at runtime
}

2. When NOT to Use Chain of Responsibility

// Avoid when:
// 1. Only one handler should process each request
public class SingleHandlerSystem {
// Use Command or Strategy pattern instead
}
// 2. The chain order is fixed and known
public class FixedPipeline {
// Consider Template Method pattern
}
// 3. Performance is critical and chain is long
public class PerformanceCriticalSystem {
// Long chains can impact performance
}

3. Combining with Other Patterns

// Chain of Responsibility + Composite
public class CompositeHandler implements Handler {
private List<Handler> children = new ArrayList<>();
public void addHandler(Handler handler) {
children.add(handler);
}
@Override
public void setNext(Handler handler) {
// Composite pattern: set next for all children
for (Handler child : children) {
child.setNext(handler);
}
}
@Override
public void handle(Request request) {
for (Handler handler : children) {
handler.handle(request);
}
}
}
// Chain of Responsibility + Observer
public class ObservableHandler extends AbstractHandler {
private List<HandlerObserver> observers = new ArrayList<>();
public void addObserver(HandlerObserver observer) {
observers.add(observer);
}
@Override
public void handle(Request request) {
notifyObservers(request, "BEFORE_PROCESSING");
super.handle(request);
notifyObservers(request, "AFTER_PROCESSING");
}
private void notifyObservers(Request request, String event) {
for (HandlerObserver observer : observers) {
observer.onHandlerEvent(this, request, event);
}
}
}
interface HandlerObserver {
void onHandlerEvent(Handler handler, Request request, String event);
}

Common Pitfalls and Solutions

1. Infinite Chain Loops

// Problem: Circular reference in chain
Handler handler1 = new ConcreteHandler();
Handler handler2 = new ConcreteHandler();
handler1.setNext(handler2);
handler2.setNext(handler1); // Circular reference!
// Solution: Validate chain setup
public class SafeHandlerChainBuilder {
private Set<Handler> handlers = new HashSet<>();
public SafeHandlerChainBuilder addHandler(Handler handler) {
if (handlers.contains(handler)) {
throw new IllegalArgumentException("Circular reference detected!");
}
handlers.add(handler);
// ... rest of builder logic
return this;
}
}

2. Performance Issues with Long Chains

// Problem: Long chains can be slow
// Solution: Use caching or short-circuiting
public class CachingHandler extends AbstractHandler {
private Map<Request, AuthResult> cache = new HashMap<>();
@Override
public void handle(Request request) {
if (cache.containsKey(request)) {
// Use cached result
AuthResult cached = cache.get(request);
System.out.println("Using cached result: " + cached.getMessage());
return;
}
// Process and cache result
super.handle(request);
cache.put(request, new AuthResult(true, true, "Cached", "USER"));
}
}

3. Debugging Complex Chains

// Problem: Hard to debug which handler processed request
// Solution: Add logging and tracing
public class TraceableHandler extends AbstractHandler {
private String name;
public TraceableHandler(String name) {
this.name = name;
}
@Override
public void handle(Request request) {
System.out.println("[" + name + "] Processing request: " + request);
if (canHandle(request)) {
System.out.println("[" + name + "] Can handle this request");
process(request);
} else {
System.out.println("[" + name + "] Passing to next handler");
super.handle(request);
}
System.out.println("[" + name + "] Finished processing");
}
@Override
protected boolean canHandle(Request request) {
// Implementation
return true;
}
@Override
protected void process(Request request) {
// Implementation
}
}

Conclusion

The Chain of Responsibility Pattern is a powerful behavioral pattern that:

  • Decouples senders and receivers of requests
  • Provides flexibility in assigning responsibilities
  • Supports dynamic handler configuration
  • Enables open/closed principle - easy to add new handlers

Key Benefits:

  1. Reduced coupling between sender and receiver
  2. Increased flexibility in assigning responsibilities
  3. Dynamic configuration of handlers at runtime
  4. Single responsibility - each handler has one purpose
  5. Open/closed principle - easy to extend with new handlers

Use When:

  • Multiple objects may handle a request
  • You want to decouple the sender from specific receivers
  • The handler isn't known in advance
  • You need dynamic handler configuration

The Chain of Responsibility Pattern is widely used in frameworks like:

  • Servlet Filters in Java EE
  • Spring Security filter chains
  • Logging frameworks
  • Event processing systems
  • Middleware pipelines

It provides an elegant solution for building flexible and maintainable processing pipelines in Java applications.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper