Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

👥10,106visitors

Built for engineers who want to go deep.


← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
Chaturmind
← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
HomeLearnSystem DesignLow-Level Design & Design PatternsBehavioral Design Patterns
✓ FreeIntermediate· 8 min read

Chain of Responsibility

Passing a request along a chain of handlers until one handles it — built through a request-validation pipeline, and how this is exactly the shape of Spring Security's own filter chain.

Published September 23, 2026


Chain of Responsibility

The idea: a pipeline of handlers, each deciding to handle or pass on

Rather than one class containing an if/else chain that checks every possible condition, Chain of Responsibility gives each check its own handler object, linked in sequence — each handler either processes the request and stops the chain, or passes it to the next handler.

abstract class RequestHandler {
    protected RequestHandler next;
    RequestHandler setNext(RequestHandler next) { this.next = next; return next; } // returns next — enables chaining the setup itself

    final void handle(Request request) {
        if (!process(request)) return; // this handler rejected the request — stop the chain here
        if (next != null) next.handle(request); // passed — hand off to the next link
    }

    protected abstract boolean process(Request request); // true = continue chain, false = reject/stop
}

class AuthCheck extends RequestHandler {
    protected boolean process(Request request) {
        if (!request.isAuthenticated()) { reject(request, 401); return false; }
        return true;
    }
}
class RateLimitCheck extends RequestHandler {
    protected boolean process(Request request) {
        if (isRateLimited(request)) { reject(request, 429); return false; }
        return true;
    }
}
class SchemaCheck extends RequestHandler {
    protected boolean process(Request request) {
        if (!isValidSchema(request)) { reject(request, 400); return false; }
        return true;
    }
}
RequestHandler chain = new AuthCheck();
chain.setNext(new RateLimitCheck()).setNext(new SchemaCheck());
chain.handle(incomingRequest); // flows through Auth -> RateLimit -> Schema, stopping at the first rejection

Each handler is independently testable, independently addable/removable, and doesn't need to know anything about the other checks in the chain — AuthCheck has no idea RateLimitCheck even exists, it just calls next.handle() if it passes.

Advertisement

This is exactly the shape of a middleware/filter-chain architecture

If this looks familiar, it should — it's structurally identical to the servlet filter chain covered in synchronized and Locks' broader context and, more directly, Spring Security's own SecurityFilterChain (see Spring Security Overview): each filter either processes the request and calls chain.doFilter() to pass it along, or short-circuits by writing a response directly and never calling doFilter(). Recognizing "this is Chain of Responsibility" the moment you see a filter/middleware/interceptor pipeline is a genuinely useful pattern-recognition shortcut — it tells you immediately how ordering matters, how a stage can short-circuit, and how to add a new stage without touching existing ones.

Two flavours: "first handler wins" vs "every handler runs"

Chain of Responsibility shows up in two shapes, and naming which one you're building avoids confusion:

  1. Handle-or-pass (the classic form): each handler either fully handles the request and stops, or passes it on. Examples: support ticket escalation (bot → agent → specialist → manager), approval limits (a team lead approves up to ₹50,000, a director up to ₹5,00,000, and above that the CFO), or picking the first parser that understands a file format.
  2. Pipeline / middleware: every handler does its part and passes the request on, and any handler may stop the chain early (by rejecting it). Examples: servlet filters, Spring Security's filter chain, Express/Koa middleware, and the auth → rate limit → schema check chain above.

The approval-limit version

record ExpenseClaim(String employeeId, BigDecimal amount, String reason) {}

abstract class Approver {
    private Approver next;
    Approver linkWith(Approver next) { this.next = next; return next; }

    final Decision approve(ExpenseClaim claim) {
        if (canApprove(claim)) return decide(claim);
        if (next == null) return Decision.rejected("No approver can authorise " + claim.amount());
        return next.approve(claim);
    }
    protected abstract boolean canApprove(ExpenseClaim claim);
    protected abstract Decision decide(ExpenseClaim claim);
}

class TeamLead extends Approver {
    protected boolean canApprove(ExpenseClaim c) { return c.amount().compareTo(new BigDecimal("50000")) <= 0; }
    protected Decision decide(ExpenseClaim c) { return Decision.approvedBy("team-lead"); }
}
class Director extends Approver {
    protected boolean canApprove(ExpenseClaim c) { return c.amount().compareTo(new BigDecimal("500000")) <= 0; }
    protected Decision decide(ExpenseClaim c) { return Decision.approvedBy("director"); }
}
class Cfo extends Approver {
    protected boolean canApprove(ExpenseClaim c) { return true; }                 // the end of the chain
    protected Decision decide(ExpenseClaim c) { return Decision.approvedBy("cfo"); }
}

Approver chain = new TeamLead();
chain.linkWith(new Director()).linkWith(new Cfo());

Note the explicit handling when no one in the chain can handle the request. Forgetting it is the most common bug in this pattern: the request silently disappears.

The modern, list-based middleware form

Linked next pointers are the textbook form. In practice, chains are usually a list that a small runner walks, which makes ordering visible and configurable in one place:

interface Middleware {
    Response handle(Request req, Chain chain);
}
interface Chain { Response proceed(Request req); }

final class Pipeline {
    private final List<Middleware> middlewares;
    private final Function<Request, Response> endpoint;

    Pipeline(List<Middleware> middlewares, Function<Request, Response> endpoint) {
        this.middlewares = List.copyOf(middlewares); this.endpoint = endpoint;
    }

    Response execute(Request req) { return step(0).proceed(req); }

    private Chain step(int i) {
        return r -> i < middlewares.size() ? middlewares.get(i).handle(r, step(i + 1)) : endpoint.apply(r);
    }
}

class TimingMiddleware implements Middleware {
    public Response handle(Request req, Chain chain) {
        long start = System.nanoTime();
        try { return chain.proceed(req); }                       // code AFTER proceed runs on the way back out
        finally { metrics.record(req.path(), System.nanoTime() - start); }
    }
}

class AuthMiddleware implements Middleware {
    public Response handle(Request req, Chain chain) {
        if (!tokens.isValid(req.header("Authorization"))) return Response.status(401);   // stop the chain
        return chain.proceed(req);
    }
}

This form handles something the handle-or-pass form doesn't: wrapping. Each middleware runs code both before and after the rest of the chain, which is exactly what timing, transactions, logging and exception translation need. It's the same design as javax.servlet.Filter (chain.doFilter(req, res)) and Spring's HandlerInterceptor/OncePerRequestFilter.

Ordering is part of the design

Order changes behaviour and cost:

  • Put cheap, rejecting checks early (IP block lists, rate limiting), so expensive work never runs for requests that will be rejected anyway.
  • Authentication before authorization, since you can't check permissions without knowing who the user is.
  • Timing and logging outermost, so they measure and record everything, including rejections.
  • Exception translation near the outside, so errors from any inner handler are converted consistently.

Spring Security makes this explicit: its filters have a defined order, and custom filters are inserted relative to named ones (addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class)).

Follow-up questions this topic invites — and their answers

Q: What happens if no handler in the chain processes the request at all? A: In the implementation above, the request falls off the end silently (no explicit final handler) — production chains typically add a terminal "default handler" at the end that either accepts (if reaching the end implies success) or explicitly rejects, so there's no ambiguous silent-fallthrough case.

Q: Can more than one handler in the chain process the same request? A: Yes, depending on design — the version above stops the chain entirely on rejection but continues past every handler that accepts, meaning all three checks run in sequence for a valid request (not just the first one). A different variant could have exactly one handler "claim" and fully handle a request, stopping the chain on acceptance too — the pattern's core shape (link handlers, pass along) accommodates either policy depending on what the problem needs.

Q: How is Chain of Responsibility different from just calling three validation methods in sequence in one function? A: Functionally similar for a fixed, known set of checks — the pattern's value shows up when the set of handlers needs to be configured, reordered, or extended without modifying a central method (matching the Open/Closed Principle), or when handlers need to be assembled differently in different contexts (e.g. a different chain for public vs authenticated endpoints).

Q: Does the order of handlers in the chain matter here, and why? A: Yes, meaningfully — AuthCheck runs before RateLimitCheck here specifically so an unauthenticated request gets a 401 rather than consuming rate-limit budget it may not even be entitled to; a different ordering (rate-limit before auth) would let unauthenticated traffic exhaust the rate limiter, a real-world reason chain ordering is a deliberate design decision, not an arbitrary one.

Q: What's the difference between the classic chain and a middleware pipeline? A: In the classic chain, exactly one handler processes the request, and the others just pass it on. In a middleware pipeline, every handler can act on the request and on the response (before and after calling the next one), and any handler can stop the chain early.

Q: How do you make the order of handlers explicit and safe? A: Keep the chain as an ordered list built in one place (configuration or a builder), not scattered setNext calls. Document or enforce order constraints, for example authentication before authorization, and test that the assembled chain is in the expected order.

Q: How do servlet filters implement this pattern? A: Each Filter.doFilter(request, response, chain) does its work, then calls chain.doFilter(...) to continue, or returns without calling it to stop processing. Code after that call runs as the response travels back out through the filters, in reverse order.

Advertisement

Previous

State Pattern

Next

Iterator & Mediator

AI Tutor

Lesson: Chain of Responsibility

Quick actions

AI responses can be inaccurate. Verify critical information.