Home/Learn/Low Level Design/Chain of Responsibility

Chain of Responsibility

Intermediate
Behavioral Patterns

Passes 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.

Java — Logger Chain of Responsibility
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 + Alert

Validation 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.

Java — Validation Chain with short-circuit
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 Aria
1

What is the difference between Chain of Responsibility and Command patterns?

MediumAmazon
2

How does Spring Security's filter chain use Chain of Responsibility?

HardGoogle
3

When should a handler stop the chain vs forward the request?

MediumNetflix
4

How would you implement a support ticket escalation using CoR?

MediumAtlassian

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.

Loading discussion…