Backend Development 20 min read

Implementing the Chain of Responsibility Pattern for Product Validation in Java

This article explains the Chain of Responsibility design pattern, demonstrates its application in a product creation workflow with concrete Java code, shows how to configure and assemble handlers dynamically using Spring, and discusses the pattern's advantages, drawbacks, and testing scenarios.

Architect's Guide
Architect's Guide
Architect's Guide
Implementing the Chain of Responsibility Pattern for Product Validation in Java

Introduction

The Chain of Responsibility pattern links multiple processing units into a chain, allowing a request to travel through each handler until one handles it or the chain ends. This article uses the pattern to validate product creation steps such as null checks, price checks, and stock checks.

Case Study 1: Multi‑Level Product Validation

Three validation steps are defined: create product, validate parameters, and save product. Each step is implemented as a separate handler (NullValueCheckHandler, PriceCheckHandler, StockCheckHandler) extending an abstract AbstractCheckHandler . The handlers are registered as Spring beans using @Component and injected via a @Resource Map<String, AbstractCheckHandler> handlerMap .

/**
 * Product data object
 */
@Data
@Builder
public class ProductVO {
    private Long skuId;
    private String skuName;
    private String imgPath;
    private BigDecimal price;
    private Integer stock;
}
/**
 * Abstract handler class
 */
@Component
public abstract class AbstractCheckHandler {
    @Getter @Setter
    protected AbstractCheckHandler nextHandler;
    @Setter @Getter
    protected ProductCheckHandlerConfig config;
    public abstract Result handle(ProductVO param);
    protected Result next(ProductVO param) {
        if (Objects.isNull(nextHandler)) {
            return Result.success();
        }
        return nextHandler.handle(param);
    }
}

The concrete handlers override handle() to perform specific checks and call super.next(param) when validation passes.

@Component
public class NullValueCheckHandler extends AbstractCheckHandler {
    @Override
    public Result handle(ProductVO param) {
        System.out.println("空值校验 Handler 开始...");
        if (super.getConfig().getDown()) {
            System.out.println("空值校验 Handler 已降级,跳过...");
            return super.next(param);
        }
        if (Objects.isNull(param) || Objects.isNull(param.getSkuId())
                || Objects.isNull(param.getPrice()) || Objects.isNull(param.getStock())) {
            return Result.failure(ErrorCode.PARAM_NULL_ERROR);
        }
        System.out.println("空值校验 Handler 通过...");
        return super.next(param);
    }
}
@Component
public class PriceCheckHandler extends AbstractCheckHandler {
    @Override
    public Result handle(ProductVO param) {
        System.out.println("价格校验 Handler 开始...");
        if (param.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
            return Result.failure(ErrorCode.PARAM_PRICE_ILLEGAL_ERROR);
        }
        System.out.println("价格校验 Handler 通过...");
        return super.next(param);
    }
}
@Component
public class StockCheckHandler extends AbstractCheckHandler {
    @Override
    public Result handle(ProductVO param) {
        System.out.println("库存校验 Handler 开始...");
        if (param.getStock() < 0) {
            return Result.failure(ErrorCode.PARAM_STOCK_ILLEGAL_ERROR);
        }
        System.out.println("库存校验 Handler 通过...");
        return super.next(param);
    }
}

Dynamic Configuration

The handler chain is built from a JSON configuration stored in a configuration center. The JSON is parsed into ProductCheckHandlerConfig , which contains the bean name, a reference to the next handler, and a downgrade flag.

/**
 * Handler configuration class
 */
@AllArgsConstructor
@Data
public class ProductCheckHandlerConfig {
    private String handler; // bean name
    private ProductCheckHandlerConfig next; // next node
    private Boolean down = Boolean.FALSE; // downgrade flag
}

The method getHandler(ProductCheckHandlerConfig config) recursively assembles the chain by retrieving beans from handlerMap , setting the config, and linking nextHandler .

private AbstractCheckHandler getHandler(ProductCheckHandlerConfig config) {
    if (Objects.isNull(config) || StringUtils.isBlank(config.getHandler())) {
        return null;
    }
    AbstractCheckHandler handler = handlerMap.get(config.getHandler());
    if (Objects.isNull(handler)) {
        return null;
    }
    handler.setConfig(config);
    handler.setNextHandler(this.getHandler(config.getNext()));
    return handler;
}

Client Execution

The HandlerClient invokes the chain via executeChain(AbstractCheckHandler handler, ProductVO param) . If any handler returns a failure, the chain stops and the error is propagated; otherwise a successful result is returned.

public class HandlerClient {
    public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
        Result handlerResult = handler.handle(param);
        if (!handlerResult.isSuccess()) {
            System.out.println("HandlerClient 责任链执行失败返回:" + handlerResult);
            return handlerResult;
        }
        return Result.success();
    }
}

Testing Scenarios

Four test cases illustrate the chain behavior: missing SKU (null value), illegal price, illegal stock, and a fully valid product. Each case prints the corresponding error or success message.

Case Study 2: Expense Reimbursement Workflow

A second example applies the same pattern to a multi‑level approval workflow where the amount determines whether the request passes through third‑, second‑, and first‑level managers. The abstract AbstractFlowHandler defines an approve() method that concrete handlers override.

Advantages and Disadvantages

Images (omitted) summarize the benefits such as decoupling, dynamic extensibility, and the pitfalls like potential performance overhead and the need to guard against infinite recursion.

Source Code

The full source repository is available at https://github.com/rongtao7/MyNotes .

backendchain of responsibilityJavaSpringValidationdesign patternHandler
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

login 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.