Chain of Responsibility Design Pattern in Java: Building Flexible Request Processing Pipelines

algo

Description / Meta Description

Learn the Chain of Responsibility Design Pattern in Java with practical examples. Understand how requests flow through multiple handlers, how Spring Security, Servlet Filters, API Gateways, and validation frameworks use this pattern, and why it helps build flexible and extensible processing pipelines.


Chain of Responsibility Design Pattern in Java: Building Flexible Request Processing Pipelines

In the previous article, we explored the Command Pattern, which helps encapsulate actions and requests as objects.

Command answers:

How do I represent an action as an object?

Chain of Responsibility answers a different question:

How do I pass a request through multiple processing steps without tightly coupling the sender to the processors?

This pattern is everywhere in enterprise applications.

If you’ve worked with:

  • Spring Security Filters
  • Servlet Filters
  • API Gateways
  • Validation Pipelines
  • Middleware Frameworks
  • Request Interceptors

then you’ve almost certainly encountered Chain of Responsibility.


The Problem: Growing Request Processing Logic

Imagine you’re building an API.

Every incoming request must go through:

Authentication
      ↓
Authorization
      ↓
Input Validation
      ↓
Rate Limiting
      ↓
Logging
      ↓
Business Logic

A naive implementation might look like:

public class RequestProcessor {

    public void process(Request request) {

        authenticate(request);

        authorize(request);

        validate(request);

        rateLimit(request);

        log(request);

        executeBusinessLogic(request);
    }
}

Looks simple initially.


Now business requirements evolve.

Additional processing appears:

Fraud Detection
Audit Logging
Encryption
Tracing
Metrics Collection
Request Transformation
Caching

Suddenly:

RequestProcessor
Becomes Huge

Difficult To Extend

Difficult To Test

Difficult To Maintain

Every new step requires modifying existing code.

This violates the:

Open/Closed Principle

What We Really Want

Instead of:

One Giant Processor

We want:

Request
    ↓

Authentication Handler
    ↓

Authorization Handler
    ↓

Validation Handler
    ↓

Logging Handler
    ↓

Business Logic Handler

Each handler performs one responsibility.

Then passes the request forward.

This is Chain of Responsibility.


What is Chain of Responsibility?

Chain of Responsibility is a Behavioral Design Pattern that:

Passes a request through a chain of handlers where each handler decides whether to process the request and whether to pass it further.

Think of it like airport security.

Passenger arrives.

Identity Check
      ↓
Security Scan
      ↓
Passport Verification
      ↓
Boarding Gate

Each checkpoint performs its responsibility.

Then forwards the passenger.


Real Life Analogy

Imagine a customer support escalation process.

Support Agent
      ↓
Team Lead
      ↓
Manager
      ↓
Director

If one level cannot resolve the issue:

Pass To Next Level

This is Chain of Responsibility.


Chain Architecture

Client
   │
   ▼

Handler 1
   │
   ▼

Handler 2
   │
   ▼

Handler 3
   │
   ▼

Handler N

Each handler decides:

Process Request?

Continue?

Stop?

Step 1: Create Handler Base Class

public abstract class Handler {

    protected Handler next;

    public Handler setNext(
            Handler next) {

        this.next = next;

        return next;
    }

    public abstract void handle(
            Request request);
}

This creates the chain.


Step 2: Create Authentication Handler

public class AuthenticationHandler
        extends Handler {

    @Override
    public void handle(
            Request request) {

        System.out.println(
            "Authentication Passed");

        if(next != null) {

            next.handle(request);
        }
    }
}

Step 3: Create Authorization Handler

public class AuthorizationHandler
        extends Handler {

    @Override
    public void handle(
            Request request) {

        System.out.println(
            "Authorization Passed");

        if(next != null) {

            next.handle(request);
        }
    }
}

Step 4: Create Logging Handler

public class LoggingHandler
        extends Handler {

    @Override
    public void handle(
            Request request) {

        System.out.println(
            "Request Logged");

        if(next != null) {

            next.handle(request);
        }
    }
}

Step 5: Build The Chain

Handler auth =
        new AuthenticationHandler();

Handler authorization =
        new AuthorizationHandler();

Handler logging =
        new LoggingHandler();

auth.setNext(authorization)
    .setNext(logging);

Chain becomes:

Authentication
      ↓
Authorization
      ↓
Logging

Step 6: Execute Request

auth.handle(request);

Output:

Authentication Passed
Authorization Passed
Request Logged

The request flows through the chain.


Why Chain of Responsibility Works

Without Chain:

Huge Method

Huge Class

Huge Conditional Logic

With Chain:

Small Independent Handlers

Composable Pipeline

Easy Extensibility

Each responsibility remains isolated.


Request Lifecycle Visualization

Incoming Request
        │
        ▼

Authentication
        │
        ▼

Authorization
        │
        ▼

Validation
        │
        ▼

Logging
        │
        ▼

Business Logic
        │
        ▼

Response

This is exactly how many enterprise frameworks operate.


Spring Security Example

One of the best examples of Chain of Responsibility.

Incoming request:

HTTP Request

Passes through:

SecurityContextFilter
      ↓
AuthenticationFilter
      ↓
AuthorizationFilter
      ↓
ExceptionFilter
      ↓
Controller

Each filter performs a single responsibility.

Then forwards the request.

Classic Chain Pattern.


Servlet Filter Example

Java Servlet Filters work the same way.

public void doFilter(
        ServletRequest request,
        ServletResponse response,
        FilterChain chain) {

    logRequest();

    chain.doFilter(
        request,
        response);
}

Notice:

chain.doFilter(...)

This passes execution to the next handler.

Pure Chain of Responsibility.


API Gateway Example

Incoming API request:

Authentication
      ↓
JWT Validation
      ↓
Rate Limiting
      ↓
Tracing
      ↓
Routing

Each stage processes and forwards.

Exactly the same pattern.


Validation Framework Example

User Registration:

Validate Name
      ↓
Validate Email
      ↓
Validate Phone
      ↓
Validate Password

Instead of:

if(...)
if(...)
if(...)
if(...)

Use handlers.

Cleaner design.


Workflow Engine Example

Loan Approval:

Credit Check
      ↓
Income Verification
      ↓
Fraud Detection
      ↓
Manager Approval

Each stage becomes a handler.

The workflow becomes flexible.


Dynamic Chain Construction

One powerful feature:

Handlers can be added
Handlers can be removed
Handlers can be reordered

Example:

chain.add(
    new AuditHandler());

chain.add(
    new MetricsHandler());

No existing code changes required.


Benefits of Chain of Responsibility

1. Loose Coupling

Sender doesn’t know processors.


2. Single Responsibility Principle

Each handler does one thing.


3. Open/Closed Principle

Add new handlers easily.


4. Reusable Components

Handlers can be reused across chains.


5. Flexible Pipelines

Chains can be configured dynamically.


Common Mistakes

Mistake 1: God Handlers

Avoid:

Authentication
Authorization
Validation
Logging

Inside One Handler

Keep handlers focused.


Mistake 2: Broken Chains

Forgetting:

next.handle(request);

stops processing unexpectedly.


Mistake 3: Excessively Long Chains

Chains with:

30+
Handlers

become difficult to debug.

Keep pipelines meaningful.


Chain of Responsibility vs Observer

Common interview question.


Observer

Purpose:

One Event
Many Subscribers

All listeners receive notification.


Chain of Responsibility

Purpose:

One Request
Many Processors

Request travels through a pipeline.


Chain of Responsibility vs Command

Command

Represents:

An Action

Example:

Generate Report
Delete User
Send Email

Chain

Represents:

Request Processing Pipeline

Example:

Authentication
Validation
Authorization

Chain of Responsibility vs Strategy

Strategy

Select one algorithm.

Card Payment
UPI Payment
Wallet Payment

Only one strategy executes.


Chain

Multiple handlers execute.

Handler 1
Handler 2
Handler 3

Pipeline processing.


When Should You Use Chain of Responsibility?

Use Chain when:

✔ Requests pass through multiple stages

✔ Processing steps vary

✔ Dynamic pipelines are required

✔ Middleware exists

✔ Validation chains exist

Examples:

  • Spring Security
  • API Gateways
  • Validation Frameworks
  • Workflow Engines
  • Middleware Pipelines
  • Servlet Filters

Avoid Chain when:

❌ Only one processor exists

❌ Processing order never changes

❌ Simpler method calls suffice


Behavioral Patterns Covered So Far

PatternPurpose
StrategyChoose algorithms dynamically
ObserverPublish-subscribe communication
CommandEncapsulate actions
Chain of ResponsibilityProcess requests through handlers

Behavioral Pattern Cheat Sheet

Strategy
→ Choose Behavior

Observer
→ Broadcast Events

Command
→ Encapsulate Actions

Chain of Responsibility
→ Process Through Pipeline

Final Thoughts

The Chain of Responsibility Pattern solves one of the most common architectural challenges:

How do we process requests through multiple independent stages without tightly coupling them together?

By creating chains of focused handlers, we gain:

  • Flexibility
  • Extensibility
  • Maintainability
  • Reusability
  • Better Separation of Concerns

This is why Chain of Responsibility powers:

  • Spring Security
  • Servlet Filters
  • API Gateways
  • Validation Pipelines
  • Middleware Frameworks
  • Enterprise Workflow Engines

Whenever you see a request flowing through multiple processing stages, you’re likely looking at a Chain of Responsibility implementation.

In the next article, we’ll explore another highly practical Behavioral Pattern:

State Design Pattern — Changing Object Behavior Dynamically Based on Its Current State

You’ll learn how order management systems, workflow engines, approval processes, ticketing systems, and finite state machines rely heavily on State Pattern concepts.

Leave a Reply

Your email address will not be published. Required fields are marked *