Chain of Responsibility
IntermediatePasses a request along a chain of handlers, each deciding to process it or forward it to the next handler.
Overview
Chain of Responsibility decouples the sender from its receivers by giving multiple objects a chance to handle the request. The chain is assembled at runtime. Each handler holds a reference to its successor and decides: handle and stop, handle and forward, or just forward. Unlike the Filter Chain variant (which always passes through), classic CoR typically stops when a handler handles the request. Use cases: multi-level logging (DEBUG→INFO→WARN→ERROR), approval workflows, validation pipelines, and Spring Security's FilterChain. The pattern is flexible but can be hard to debug when the chain is long.
Logger Chain
Classic CoR example: a logger chain where each level handles messages at or above its threshold. Messages propagate up the chain until handled.
public enum LogLevel { DEBUG, INFO, WARN, ERROR }
// Abstract Handler
public abstract class Logger {
protected final LogLevel level;
protected Logger next;
public Logger(LogLevel level) { this.level = level; }
public Logger setNext(Logger next) {
this.next = next;
return next;
}
public final void log(LogLevel msgLevel, String message) {
if (msgLevel.ordinal() >= this.level.ordinal()) {
write(message); // this handler can process it
}
if (next != null) {
next.log(msgLevel, message); // always forward (unlike classic CoR stop)
}
}
protected abstract void write(String message);
}
// Concrete Handlers
public class ConsoleLogger extends Logger {
public ConsoleLogger(LogLevel level) { super(level); }
@Override protected void write(String msg) {
System.out.println("[CONSOLE] " + msg);
}
}
public class FileLogger extends Logger {
public FileLogger(LogLevel level) { super(level); }
@Override protected void write(String msg) {
System.out.println("[FILE] " + msg); // writes to rotating log file
}
}
public class AlertLogger extends Logger {
public AlertLogger(LogLevel level) { super(level); }
@Override protected void write(String msg) {
System.out.println("[ALERT] " + msg); // sends PagerDuty alert
}
}
// Build chain: Console handles DEBUG+, File handles WARN+, Alert handles ERROR+
Logger chain = new ConsoleLogger(LogLevel.DEBUG);
chain.setNext(new FileLogger(LogLevel.WARN))
.setNext(new AlertLogger(LogLevel.ERROR));
chain.log(LogLevel.DEBUG, "Starting application"); // Console only
chain.log(LogLevel.WARN, "High memory usage"); // Console + File
chain.log(LogLevel.ERROR, "Database unreachable"); // Console + File + AlertValidation Chain
A validation pipeline where each validator checks one rule and either adds an error or passes to the next. Unlike the logger (which always forwards), a strict validation chain can short-circuit on first failure.
public class RegistrationRequest {
public final String username;
public final String email;
public final String password;
public RegistrationRequest(String u, String e, String p) {
username = u; email = e; password = p;
}
}
public abstract class Validator {
protected Validator next;
public Validator setNext(Validator v) { this.next = v; return v; }
public abstract Optional<String> validate(RegistrationRequest req);
protected Optional<String> passToNext(RegistrationRequest req) {
return next != null ? next.validate(req) : Optional.empty();
}
}
public class UsernameValidator extends Validator {
@Override
public Optional<String> validate(RegistrationRequest req) {
if (req.username == null || req.username.length() < 3)
return Optional.of("Username must be at least 3 characters");
return passToNext(req); // pass to next validator
}
}
public class EmailValidator extends Validator {
@Override
public Optional<String> validate(RegistrationRequest req) {
if (req.email == null || !req.email.contains("@"))
return Optional.of("Invalid email format");
return passToNext(req);
}
}
public class PasswordValidator extends Validator {
@Override
public Optional<String> validate(RegistrationRequest req) {
if (req.password == null || req.password.length() < 8)
return Optional.of("Password must be at least 8 characters");
return passToNext(req);
}
}
// Build chain
Validator chain = new UsernameValidator();
chain.setNext(new EmailValidator()).setNext(new PasswordValidator());
Optional<String> error = chain.validate(new RegistrationRequest("ak", "akshay@", "pass"));
error.ifPresentOrElse(
e -> System.out.println("Validation failed: " + e),
() -> System.out.println("Validation passed")
);Key Points to Remember
- 1CoR decouples sender from receiver — the sender does not know which handler processes the request.
- 2Classic CoR stops at the first handler that processes; Filter Chain always forwards unless short-circuited.
- 3Handlers can be added/removed/reordered at runtime without changing client code.
- 4Spring Security Filter Chain processes authentication/authorization through a fixed sequence of filters.
- 5Debugging long chains is hard — consider adding logging at each handler for traceability.
Interview Questions
Sign in to ask AriaWhat is the difference between Chain of Responsibility and Command patterns?
How does Spring Security's filter chain use Chain of Responsibility?
When should a handler stop the chain vs forward the request?
How would you implement a support ticket escalation using CoR?
Ask Aria about Chain of Responsibility
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.