Fundamentals 21 min read

Master the Chain of Responsibility Pattern in Java for Real-World Validation

This article explains the Chain of Responsibility design pattern, demonstrates its implementation with Spring‑managed handlers for product creation validation and expense‑approval workflows, shows how to configure and dynamically compose handlers via JSON, and provides complete Java code examples and diagrams.

Programmer DD
Programmer DD
Programmer DD
Master the Chain of Responsibility Pattern in Java for Real-World Validation

Introduction

Reading this article you will learn how the Chain of Responsibility pattern works, how it enables workflow orchestration and dynamic extension, how to inject handlers with Spring @Resource, and how to apply recursive configuration for flexible validation.

What you will learn

Experience the power of the Chain of Responsibility pattern through concrete examples.

Implement process orchestration and dynamic extension using the pattern.

Use Spring @Resource for handler injection.

Apply recursive algorithms to build the handler chain.

Brief Overview

The Chain of Responsibility pattern assembles multiple operations into a linked chain. A request travels along the chain; each node (handler) can process the request or pass it to the next handler.

Application Scenarios

Typical scenarios include:

Operations that require a series of validations before execution.

Workflows where tasks are processed level by level.

Case 1: Multi‑Level Product Validation

Creating a product involves three steps: create, validate parameters, and save. Validation is split into several checks (null, price, stock) forming a pipeline.

Pseudocode

createProduct() {
    // run a series of parameter checks; if any fails, return error
    // if all pass, save the product
}

The initial implementation becomes messy as validation logic grows, leading to low maintainability and code duplication.

Refactoring with the Chain of Responsibility separates each validation step into its own handler, making the code reusable and easier to extend.

UML Overview

The abstract class AbstractCheckHandler defines the template method handle() and the chain navigation method next(). Concrete handlers ( NullValueCheckHandler, PriceCheckHandler, StockCheckHandler) implement their specific validation logic.

public abstract class AbstractCheckHandler {
    protected AbstractCheckHandler nextHandler;
    protected ProductCheckHandlerConfig config;
    public abstract Result handle(ProductVO param);
    protected Result next(ProductVO param) {
        if (nextHandler == null) return Result.success();
        return nextHandler.handle(param);
    }
}

Each concrete handler checks its own rule and, if successful, calls super.next(param). Handlers can be disabled via the down flag in the configuration.

@Component
public class NullValueCheckHandler extends AbstractCheckHandler {
    @Override
    public Result handle(ProductVO param) {
        if (config.getDown()) return super.next(param);
        if (param == null) return Result.failure(ErrorCode.PARAM_NULL_ERROR);
        if (param.getSkuId() == null) return Result.failure(ErrorCode.PARAM_SKU_NULL_ERROR);
        // other checks …
        return super.next(param);
    }
}

The configuration class ProductCheckHandlerConfig stores the bean name of the handler, a reference to the next handler, and a downgrade flag.

public class ProductCheckHandlerConfig {
    private String handler;
    private ProductCheckHandlerConfig next;
    private Boolean down = Boolean.FALSE;
}

Handler retrieval uses a Map<String, AbstractCheckHandler> injected by Spring. The method getHandler(config) validates the configuration, sets the handler’s config, and recursively builds the chain.

@Resource
private Map<String, AbstractCheckHandler> handlerMap;

private AbstractCheckHandler getHandler(ProductCheckHandlerConfig config) {
    if (config == null) return null;
    AbstractCheckHandler h = handlerMap.get(config.getHandler());
    if (h == null) return null;
    h.setConfig(config);
    h.setNextHandler(getHandler(config.getNext()));
    return h;
}

The client executes the chain via HandlerClient.executeChain(handler, param). If any handler returns a failure, the chain stops and the error is propagated.

public class HandlerClient {
    public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
        Result r = handler.handle(param);
        if (!r.isSuccess()) {
            System.out.println("Chain failed: " + r);
            return r;
        }
        return Result.success();
    }
}

Four test scenarios demonstrate how missing values, illegal price, or illegal stock cause the chain to abort, while a fully valid product passes all handlers and is saved.

Case 2: Expense Reimbursement Workflow

A multi‑level approval process (level‑3, level‑2, level‑1 managers) is modeled with the same pattern. Each level’s handler checks the amount range and forwards to the next level if needed. The configuration can be changed at runtime to adjust approval limits.

Pros and Cons of the Chain of Responsibility

Source Code

https://github.com/rongtao7/MyNotes
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaConfigurationvalidation
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.