Back to Behavioral: Strategy, Observer, Command, State, Template
Q8Question 8 of 12

Design using the Chain of Responsibility pattern

Key Concepts
  • The Chain of Responsibility passes a request through a line of handlers until one handles it or the chain ends.
  • Each handler holds a reference to the next handler and either processes the request or forwards it.
  • The pattern suits two jobs: a validation pipeline where all checks must pass, and an approval workflow where the first capable handler acts.
  • Adding a new validation or approval level is a new handler class inserted into the chain, satisfying Open/Closed.
  • A request that no handler accepts must have a clear default outcome, not silently vanish.
  • The chain can be built dynamically from configuration, making it flexible for different branches or times of day.

Definition

The Chain of Responsibility passes a request through a line of handlers until one handles it or all have run.
  • Chain of Responsibility – passes a request along a chain of handlers, each deciding to process it or forward to the next.
  • Handler – holds a reference to the next handler and a handle() method.
  • Validation pipeline – every handler checks one rule; all must pass for the request to proceed.
  • Approval workflow – each handler checks its authority; the first capable handler processes and stops.
  • Extensibility – adding a new step is inserting a new handler with zero changes to existing ones.

Think of the Chain as a loan approval route at National Bank of India. Rajesh's loan request passes first to the clerk, then the branch manager, then the regional head, each approving within their limit or escalating. The request flows down the chain until someone handles it or it runs out of handlers.

Understanding the Concepts

What is the Chain of Responsibility for validation and approval?

The Chain of Responsibility turns a long if-else or a monolithic validator into a set of small, independent handlers that can be reordered or extended freely. Each handler holds a reference to the next and either processes the request or forwards it.

JAVA
package com.bank.chain;

import java.math.BigDecimal;

public abstract class ValidationHandler {
    private ValidationHandler next;

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

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

    protected abstract void check(TransferRequest request);
}

How do you implement a validation pipeline?

Each handler checks one rule and either rejects the request (by throwing) or forwards it. All handlers must pass for the request to proceed.

JAVA
package com.bank.chain;

import java.math.BigDecimal;

public class AmountValidator extends ValidationHandler {
    @Override
    protected void check(TransferRequest request) {
        if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException(
                "Amount must be positive, got: " + request.getAmount());
        }
    }
}

public class KycValidator extends ValidationHandler {
    @Override
    protected void check(TransferRequest request) {
        if (!request.isKycComplete()) {
            throw new IllegalStateException(
                "KYC not complete for account: " + request.getFromId());
        }
    }
}

public class DailyLimitValidator extends ValidationHandler {
    private static final BigDecimal DAILY_LIMIT = new BigDecimal("500000");

    @Override
    protected void check(TransferRequest request) {
        if (request.getAmount().compareTo(DAILY_LIMIT) > 0) {
            throw new IllegalArgumentException(
                "Amount INR " + request.getAmount()
                + " exceeds daily limit of INR " + DAILY_LIMIT);
        }
    }
}

How do you wire the validation chain?

Build the chain by linking handlers, then send requests to the first handler.

JAVA
package com.bank.demo;

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

public class ValidationChainDemo {
    public static void main(String[] args) {
        ValidationHandler amount = new AmountValidator();
        ValidationHandler kyc = new KycValidator();
        ValidationHandler limit = new DailyLimitValidator();

        amount.setNext(kyc);
        kyc.setNext(limit);

        TransferRequest request = new TransferRequest(
            "SAV2001", "SAV3002",
            new BigDecimal("100000"), true);

        try {
            amount.validate(request);
            System.out.println("All validations passed");
        } catch (Exception e) {
            System.out.println("Validation failed: " + e.getMessage());
        }
    }
}
TEXT
All validations passed

How do you implement an approval workflow?

In approval mode, each handler checks its authority. The first capable handler processes and stops.

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 {
            System.out.println("REJECTED: exceeds all approval limits");
        }
    }

    protected abstract void doApprove(TransferRequest request);
}
JAVA
ApprovalHandler clerk = new ClerkApproval();
ApprovalHandler officer = new OfficerApproval();
ApprovalHandler manager = new ManagerApproval();

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

clerk.approve(new TransferRequest("SAV2001", "SAV3002",
    new BigDecimal("300000"), true));
TEXT
Officer approved transfer of INR 300000

How is validation different from approval?

In a validation pipeline, every handler runs in sequence: all checks must pass. In an approval workflow, exactly one handler processes: the first one with enough authority.

DIAGRAM
| Aspect | Validation Pipeline | Approval Workflow |
|---|---|---|
| Who processes? | Every handler runs | First capable handler |
| Failure | Any handler rejects | No capable handler found |
| Example | Amount + KYC + limit checks | Clerk → Officer → Manager |

How do you add a new step?

Adding a fraud-score check is a new handler inserted into the chain. No existing handler is modified.

JAVA
public class FraudScoreValidator extends ValidationHandler {
    @Override
    protected void check(TransferRequest request) {
        int score = fraudService.getScore(request.getFromId());
        if (score < 30) {
            throw new SecurityException(
                "Fraud score too low: " + score);
        }
    }
}

// Insert into existing chain
kyc.setNext(new FraudScoreValidator());
// The new handler's next is set to the limit validator

How do you build the chain dynamically?

The chain can be built from configuration so different branches get different rules.

JAVA
package com.bank.config;

import com.bank.chain.ValidationHandler;
import java.util.List;

public class ChainBuilder {
    public static ValidationHandler build(List<ValidationHandler> handlers) {
        for (int i = 0; i < handlers.size() - 1; i++) {
            handlers.get(i).setNext(handlers.get(i + 1));
        }
        return handlers.get(0);
    }
}
JAVA
List<ValidationHandler> handlers = List.of(
    new AmountValidator(),
    new KycValidator(),
    new FraudScoreValidator(),
    new DailyLimitValidator()
);
ValidationHandler chain = ChainBuilder.build(handlers);
chain.validate(request);

What are common mistakes?

The first mistake is not handling the end of the chain. If no handler can process the request, it should be explicitly rejected, not silently dropped.

The second mistake is putting multiple responsibilities in one handler, which defeats the purpose of the chain.

JAVA
// Wrong: handler checks amount AND KYC (two responsibilities)
public class BigValidator extends ValidationHandler {
    protected void check(TransferRequest r) {
        if (r.getAmount().compareTo(BigDecimal.ZERO) <= 0) { ... }
        if (!r.isKycComplete()) { ... } // should be separate handler
    }
}

What should a senior engineer know?

At National Bank of India, Pankaj builds transfer validation and loan approval using Chain of Responsibility. A regulated money operation must pass a precise, auditable sequence of independent checks, and the chain lets the team compose and evolve that sequence safely. Each handler is trivially testable in isolation because it does one thing.

Visual Overview

DIAGRAM
  Validation Pipeline
  ───────────────────
  Request ──→ AmountValidator ──→ KycValidator ──→ LimitValidator
              (positive?)         (KYC done?)      (within limit?)
              All must pass for the request to proceed.
DIAGRAM
  Approval Workflow
  ─────────────────
  Request (INR 3,00,000)
       │
       ▼
  Clerk (limit: 50K)  ──→ cannot handle
       │
       ▼
  Officer (limit: 5L) ──→ APPROVED
       │
  (Manager never reached)

Key Takeaway

The Chain of Responsibility passes a request along a line of handlers, each deciding whether to process it or forward it to the next. This decouples the sender from the receivers and turns a monolithic validator or a giant if-else into a set of small, independent handlers that can be reordered, enabled, disabled, or extended freely.

The pattern suits two classic jobs. A validation pipeline runs every handler in sequence: each checks one rule, and all must pass. An approval workflow has each handler check its authority, and the first capable handler processes and stops. Adding a new step is a new handler class inserted into the chain with zero changes to existing handlers, which is the Open/Closed principle.

The big design advantage is that each handler is trivially testable in isolation because it does one thing, and the chain can be built dynamically from configuration so different branches or times of day get different rules. The critical safety rule is that a request that no handler accepts must have an explicit default outcome rather than silently vanishing.

At National Bank of India, Pankaj builds transfer validation and loan approval using Chain of Responsibility. A regulated money operation must pass a precise, auditable sequence of independent checks, and the chain lets the team compose and evolve that sequence safely.

Remember one sentence: Chain of Responsibility passes a request through independent handlers, decoupling the sender from the receivers and making the processing pipeline freely extensible.

---

Share
Use ← → arrow keys to navigate