How to Implement Chain of Responsibility for Request Handling Pipelines in Java
The Chain of Responsibility pattern decouples senders from receivers by routing requests through a sequence of handlers, each deciding whether to process the request or pass it to the next handler in the pipeline.
The Chain of Responsibility pattern is essential for building flexible request handling pipelines where multiple objects can process a request without hard-coding the receiver. In the java-design-patterns repository by iluwatar, the chain-of-responsibility module demonstrates a robust implementation that separates request objects from handler logic using priority-based routing. This guide walks through the source code architecture and provides a complete implementation strategy for production Java applications.
Understanding the Chain of Responsibility Pattern
The pattern creates a loosely coupled pipeline where a request traverses a chain of handler objects until one capable handler processes it. Each handler implements a common interface exposing canHandleRequest() for capability checking and handle() for execution. The chain structure allows dynamic handler registration and reordering without modifying client code.
Core Components in the java-design-patterns Implementation
The Request Object
Located in [Request.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/Request.java), this immutable data class encapsulates request metadata including RequestType and description, plus a mutable handled flag tracked via isHandled() and markHandled() methods.
The Handler Interface
[RequestHandler.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestHandler.java) defines the contract with four essential methods:
boolean canHandleRequest(Request)– checks processing capabilityint getPriority()– determines execution order (lower values execute first)void handle(Request)– executes business logicString name()– identifies the handler for logging/debugging
Concrete Handler Implementations
The repository provides three concrete implementations demonstrating type-specific handling:
- OrcSoldier ([
OrcSoldier.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcSoldier.java)) – handlesCOLLECT_TAXrequests with priority 1 - OrcCommander ([
OrcCommander.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcCommander.java)) – handlesDEFEND_CASTLErequests with priority 2 - OrcOfficer ([
OrcOfficer.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcOfficer.java)) – handlesTORTURE_PRISONERrequests with priority 3
The Chain Orchestrator
[OrcKing.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcKing.java) serves as the client that constructs the handler pipeline. It stores handlers in a List<RequestHandler> and processes requests by streaming through the list, sorting by getPriority(), filtering for the first capable handler via canHandleRequest(), and invoking handle().
Step-by-Step Implementation Guide
-
Define the Request Model – Create an immutable class containing request data and a mutable processed flag to prevent duplicate handling.
-
Create the Handler Interface – Declare methods for capability checking, priority retrieval, processing logic, and identification.
-
Implement Concrete Handlers – Build specialized handlers that override
canHandleRequest()to filter by request type or business rules, assigning appropriate priority values. -
Build the Pipeline – Collect handlers into a collection; the execution order is determined by sorting
getPriority()values rather than insertion order. -
Process Requests – Iterate the sorted handler chain, route to the first capable handler, and execute its
handle()method. -
Enable Runtime Extensibility – Expose methods to append handlers dynamically or inject them via dependency injection frameworks like Spring.
Practical Java Implementation Example
The following standalone example mirrors the repository's architecture while demonstrating a minimal logging pipeline:
import java.util.*;
import java.util.Objects;
// 1. Request model
public final class Request {
private final RequestType type;
private final String description;
private boolean handled;
public Request(RequestType type, String description) {
this.type = Objects.requireNonNull(type);
this.description = Objects.requireNonNull(description);
}
public RequestType getType() { return type; }
public String getDescription() { return description; }
public boolean isHandled() { return handled; }
public void markHandled() { this.handled = true; }
}
enum RequestType {
COLLECT_TAX, DEFEND_CASTLE, TORTURE_PRISONER
}
// 2. Handler contract
interface RequestHandler {
boolean canHandle(Request request);
int getPriority();
void handle(Request request);
String name();
}
// 3. Concrete handler
class LoggingHandler implements RequestHandler {
public boolean canHandle(Request r) { return true; }
public int getPriority() { return 10; }
public void handle(Request r) {
System.out.println("Logging request: " + r.getDescription());
r.markHandled();
}
public String name() { return "Logger"; }
}
// 4. Pipeline builder
class RequestPipeline {
private final List<RequestHandler> handlers;
public RequestPipeline(RequestHandler... hs) {
this.handlers = Arrays.asList(hs);
}
public void process(Request request) {
handlers.stream()
.sorted(Comparator.comparingInt(RequestHandler::getPriority))
.filter(h -> h.canHandle(request))
.findFirst()
.ifPresentOrElse(
h -> h.handle(request),
() -> System.out.println("No handler for " + request.getDescription())
);
}
}
// 5. Usage
public class ChainDemo {
public static void main(String[] args) {
Request taxRequest = new Request(RequestType.COLLECT_TAX, "collect tax");
RequestPipeline pipeline = new RequestPipeline(new LoggingHandler());
pipeline.process(taxRequest);
}
}
Key Source Files in the Repository
The java-design-patterns implementation consists of these critical files:
-
[
Request.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/Request.java) – Immutable data class containingRequestType, description, and thehandledflag withmarkHandled()state management. -
[
RequestType.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestType.java) – Enumeration defining supported request types (DEFEND_CASTLE,COLLECT_TAX,TORTURE_PRISONER). -
[
RequestHandler.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestHandler.java) – Interface definingcanHandleRequest(),getPriority(),handle(), andname()methods that all chain members must implement. -
[
OrcKing.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcKing.java) – Client class that aggregates handlers into aList<RequestHandler>and executes the routing logic using Java Streams. -
[
OrcCommander.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcCommander.java) – Concrete handler processingDEFEND_CASTLErequests with priority level 2. -
[
OrcOfficer.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcOfficer.java) – Concrete handler processingTORTURE_PRISONERrequests with priority level 3. -
[
OrcSoldier.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcSoldier.java) – Concrete handler processingCOLLECT_TAXrequests with priority level 1. -
[
App.java](https://github.com/iluwatar/java-design-patterns/blob/master/chain-of-responsibility/src/main/java/com/iluwatar/chain/App.java) – Demonstration entry point that instantiates the king and dispatches sample requests.
Summary
- The Chain of Responsibility pattern eliminates tight coupling between request senders and receivers by introducing a dynamic handler pipeline.
- The java-design-patterns implementation uses a priority-based sorting mechanism via
getPriority()to determine handler execution order rather than hard-coded link references. - Each handler explicitly declares its capabilities through
canHandleRequest(), enabling type-specific routing without conditional logic in the client. - The
OrcKingclient demonstrates modern Java practices by using Streams to filter and select the first capable handler from the chain.
Frequently Asked Questions
What is the main advantage of using Chain of Responsibility for request handling?
The primary advantage is decoupling—the client that sends a request does not need to know which specific object will handle it. This allows handler chains to be reorganized, extended with new handlers, or modified at runtime without changing the client code in OrcKing.java or the request originator.
How does the java-design-patterns implementation differ from traditional linked-list chain structures?
Unlike traditional implementations that use setSuccessor() to link handlers into a chain, the java-design-patterns version stores handlers in a List<RequestHandler> and uses Java Streams to sort by getPriority() and filter by canHandleRequest(). This approach leverages functional programming patterns and makes the execution order data-driven rather than structurally hard-coded.
Can handlers pass requests to multiple successors or stop processing entirely?
Yes, handlers can modify the request state and continue the chain, or they can terminate processing by not calling subsequent handlers. In the provided implementation, once handle() is invoked on the first capable handler, the stream terminates via findFirst(), but you could modify OrcKing.java to iterate through all handlers for logging or auditing purposes by removing the findFirst() short-circuit.
What happens if no handler in the chain can process a request?
If no handler returns true from canHandleRequest(), the findFirst() operation returns an empty Optional, and the ifPresentOrElse() block executes the fallback action—typically logging a warning or throwing an exception. You should always provide a default handler or empty handler check to prevent silent request drops in production pipelines.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →