Back to Behavioral: Strategy, Observer, Command, State, Template
Q2Question 2 of 12

Chain of Responsibility pattern: passing a request along a chain of handlers (approval-escalation example)

Key Concepts
  • A request walks down a line of handlers, and the first one capable of handling it does so; the sender never picks the handler.
  • Each handler holds a reference to the next handler and decides whether to process or forward the request.
  • The sender is decoupled from the concrete receivers, so adding a new handler requires zero changes to existing ones.
  • Two variations exist: pure CoR where exactly one handler processes, and pipeline where every handler runs in sequence.
  • Spring Security's filter chain is a real production example of Chain of Responsibility.
  • A terminal handler must always exist to prevent requests from silently vanishing.

Definition

A request walks down a line of handlers, and the first one capable of dealing with it does so: the sender never picks the handler.
  • Chain of Responsibility – a behavioral pattern that passes a request along a chain of handler objects.
  • Handler – an abstract class or interface declaring a method to handle the request and holding a reference to the next handler.
  • ConcreteHandler – implements the handling logic and decides whether to process or forward.
  • Decoupling – the sender has no idea which handler will ultimately process the request.
  • Extensibility – adding a new handler is inserting a new link in the chain with zero changes to existing handlers.

Imagine Rajesh walks into National Bank of India and asks to approve a large transfer of twelve lakh rupees. The junior clerk can approve up to fifty thousand, so the clerk passes it to the branch officer who can approve up to five lakh. Still not enough, so the officer passes it to the branch manager who can approve up to twenty lakh. The manager approves it. Rajesh never had to know who would approve it: the request travelled up the chain until someone with enough authority handled it.

Understanding the Concepts

What is the Chain of Responsibility pattern?

Chain of Responsibility is a behavioral pattern where a request is passed along a chain of handler objects. Each handler decides either to process the request or to pass it to the next handler. The sender never knows which handler will ultimately deal with it.

JAVA
package com.bank.chain;

import java.math.BigDecimal;

public abstract class ApprovalHandler {
    private ApprovalHandler next;
    protected final BigDecimal limit;

    protected ApprovalHandler(BigDecimal limit) {
        this.limit = limit;
    }

    public void setNext(ApprovalHandler next) {
        this.next = next;
    }

    public void approve(TransferRequest request) {
        if (request.getAmount().compareTo(limit) <= 0) {
            doApprove(request);
        } else if (next != null) {
            next.approve(request);
        } else {
            reject(request);
        }
    }

    protected abstract void doApprove(TransferRequest request);

    private void reject(TransferRequest request) {
        System.out.println("REJECTED: No handler can approve INR "
                         + request.getAmount());
    }
}

Why does the Chain of Responsibility exist?

Without this pattern, the approval logic would be one giant if-else method that every developer is afraid to touch. Adding a new approval tier means editing that method, which risks breaking existing tiers.

JAVA
// Without chain: monolithic if-else (bad)
if (amount <= 50000) { clerk.approve(); }
else if (amount <= 500000) { officer.approve(); }
else if (amount <= 2000000) { manager.approve(); }
// Adding a new tier means editing this code

With the chain, each tier is its own class, and adding a new tier is just inserting a new handler.

How do you implement concrete handlers?

Each handler extends the abstract handler and provides its own approval logic.

JAVA
package com.bank.chain;

import java.math.BigDecimal;

public class ClerkHandler extends ApprovalHandler {
    public ClerkHandler() { super(new BigDecimal("50000")); }

    @Override
    protected void doApprove(TransferRequest request) {
        System.out.println("Clerk approved transfer of INR "
                         + request.getAmount());
    }
}

public class OfficerHandler extends ApprovalHandler {
    public OfficerHandler() { super(new BigDecimal("500000")); }

    @Override
    protected void doApprove(TransferRequest request) {
        System.out.println("Officer approved transfer of INR "
                         + request.getAmount());
    }
}

public class ManagerHandler extends ApprovalHandler {
    public ManagerHandler() { super(new BigDecimal("2000000")); }

    @Override
    protected void doApprove(TransferRequest request) {
        System.out.println("Manager approved transfer of INR "
                         + request.getAmount());
    }
}

How do you wire and use the chain?

Build the chain by setting the next handler for each link, then send requests to the first handler.

JAVA
package com.bank.demo;

import com.bank.chain.*;
import java.math.BigDecimal;

public class ChainDemo {
    public static void main(String[] args) {
        ApprovalHandler clerk = new ClerkHandler();
        ApprovalHandler officer = new OfficerHandler();
        ApprovalHandler manager = new ManagerHandler();

        clerk.setNext(officer);
        officer.setNext(manager);

        clerk.approve(new TransferRequest("SAV2001", new BigDecimal("30000")));
        clerk.approve(new TransferRequest("SAV2001", new BigDecimal("300000")));
        clerk.approve(new TransferRequest("SAV2001", new BigDecimal("1200000")));
        clerk.approve(new TransferRequest("SAV2001", new BigDecimal("5000000")));
    }
}
TEXT
Clerk approved transfer of INR 30000
Officer approved transfer of INR 300000
Manager approved transfer of INR 1200000
REJECTED: No handler can approve INR 5000000

How is Chain of Responsibility different from a pipeline?

In pure CoR (approval escalation), exactly one handler processes the request. In a pipeline (validation), every handler runs in sequence and all checks must pass.

| Aspect | Pure CoR | Pipeline |
|---|---|---|
| Who processes? | First capable handler | Every handler in sequence |
| Example | Approval escalation | Validation checks |
| Stop condition | One handler handles it | One handler rejects it |

JAVA
// Pipeline variant: every handler checks one rule
public abstract class ValidationHandler {
    private ValidationHandler next;

    public void validate(TransferRequest request) {
        check(request); // throws if invalid
        if (next != null) next.validate(request);
    }

    protected abstract void check(TransferRequest request);
}

What happens if no handler can process the request?

If the chain ends without any handler processing the request, it can silently vanish. In banking this is dangerous. Always add a terminal handler or an explicit reject step.

JAVA
// Wrong: request silently vanishes
if (next != null) { next.approve(request); }
// else: nothing happens (dangerous)

// Correct: explicit rejection at chain end
if (next != null) {
    next.approve(request);
} else {
    log.severe("No handler for INR " + request.getAmount());
    throw new UnsupportedOperationException(
        "Transfer exceeds all approval limits");
}

What are real-world examples?

Spring Security's filter chain is Chain of Responsibility: each Filter either handles the request or calls chain.doFilter(request, response). The Servlet FilterChain is the same pattern at framework level.

JAVA
// Spring Security filter chain (conceptual)
public class AuthenticationFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
                         FilterChain chain) throws IOException {
        if (isAuthenticated(req)) {
            chain.doFilter(req, resp); // pass to next filter
        } else {
            resp.sendError(401); // handle here, stop chain
        }
    }
}

How do you make the chain dynamic?

The chain can be built from configuration, so different branches or times of day get different approval tiers. This makes the pattern highly flexible.

JAVA
// Build chain from configuration
List<ApprovalHandler> handlers = config.getApprovalTiers().stream()
    .map(tier -> createHandler(tier))
    .collect(Collectors.toList());

for (int i = 0; i < handlers.size() - 1; i++) {
    handlers.get(i).setNext(handlers.get(i + 1));
}
// Now the chain is configured dynamically

What should a senior engineer know?

Each handler should be focused on a single decision, which respects the Single Responsibility Principle and keeps the chain easy to reorder. When the business adds a new tier, like a regional head for transfers above twenty lakh, Rajesh writes one new handler class and wires it at the end. No existing handler code is touched, which is the Open/Closed Principle in action.

JAVA
// Adding a new tier: one new class, zero changes to existing code
public class RegionalHeadHandler extends ApprovalHandler {
    public RegionalHeadHandler() { super(new BigDecimal("10000000")); }

    @Override
    protected void doApprove(TransferRequest request) {
        System.out.println("Regional Head approved INR " + request.getAmount());
    }
}

manager.setNext(new RegionalHeadHandler()); // just wire it in

Visual Overview

DIAGRAM
  Transfer Request (INR 12,00,000)
       │
       ▼
  ┌──────────────┐  limit: 50,000
  │    Clerk      │──→ cannot handle
  └──────┬───────┘
         │ forwards
         ▼
  ┌──────────────┐  limit: 5,00,000
  │   Officer     │──→ cannot handle
  └──────┬───────┘
         │ forwards
         ▼
  ┌──────────────┐  limit: 20,00,000
  │   Manager     │──→ APPROVED
  └──────────────┘
DIAGRAM
  Pipeline variant (validation)
  ─────────────────────────────
  Request ──→ AmountValidator ──→ KycValidator ──→ LimitValidator
              (check amount)      (check KYC)      (check limit)
              All must pass for the request to proceed

Key Takeaway

Chain of Responsibility is a behavioral pattern where a request is passed along a chain of handler objects, and each handler either processes the request or forwards it to the next. The real power is decoupling: the sender does not need to know which handler will ultimately deal with it, which keeps the sender simple and makes the handler set easy to extend and reorder.

In the banking project at National Bank of India, Rajesh uses this pattern for transfer approval escalation. Different tiers: a clerk approves up to fifty thousand, an officer up to five lakh, and a manager up to twenty lakh. Instead of one giant if-else method, each tier is its own handler class. The teller submits the request to the first handler, and it travels up the chain until someone with enough authority approves it.

The benefit is clean extensibility. When the business adds a regional head tier for transfers above twenty lakh, Rajesh writes one new handler class and wires it at the end. No existing code is touched, which is the Open/Closed Principle. Each handler also has a single, clear responsibility, so the code is easy to test.

One production detail to always stress is the terminal case. If a request exceeds every handler's authority and there is no fallback, it can silently vanish, which is dangerous in finance. A real-world example of this pattern at framework level is the Spring Security filter chain, where each filter either handles the request or calls the chain to pass it forward.

Remember one sentence: Chain of Responsibility passes a request down a line of handlers, each processing or forwarding, so the sender is decoupled from the receivers and the chain is freely extensible.

---

Share
Use ← → arrow keys to navigate