Fundamentals 20 min read

Advanced Chain of Responsibility: Multi‑Level Validation and Workflow for Elegant Code

This article explains the Chain of Responsibility pattern, shows how to use it for multi‑stage validation and workflow scenarios, walks through a product‑creation example with concrete handlers, configuration via JSON, Spring bean injection, recursive chain assembly, and a reimbursement‑approval workflow, complete with runnable test cases.

Programmer XiaoFu
Programmer XiaoFu
Programmer XiaoFu
Advanced Chain of Responsibility: Multi‑Level Validation and Workflow for Elegant Code

Application Scenarios

The Chain of Responsibility pattern can be used when an operation must pass through a series of validations before execution, or to model hierarchical workflows where tasks are processed level by level.

Multi‑step validation before performing an action.

Enterprise workflows that progress through successive approval stages.

Case 1: Multi‑Level Validation for Product Creation

Creating a product involves three logical steps: create the product entity, validate its parameters, and finally persist it. Validation itself is split into several checks—required fields, specification, price, stock, etc.—forming a pipeline.

Pseudocode shows the overall flow: if any validation fails, the process returns an error; otherwise the product is saved.

When the validation logic grows, the code becomes bulky and hard to maintain. Refactoring with the Chain of Responsibility extracts each validation step into an independent processor class.

Abstract Handler and Concrete Handlers

The abstract class AbstractCheckHandler defines the template method handle() and holds a reference to the next handler, a configuration object, and a protected next() method that forwards the request. NullValueCheckHandler: checks required fields, SKU, price, and stock. PriceCheckHandler: validates that the price is greater than zero. StockCheckHandler: validates that stock is non‑negative.

public abstract class AbstractCheckHandler {
    protected AbstractCheckHandler nextHandler;
    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);
    }
}

Each concrete handler overrides handle(), performs its specific check, and either returns a failure Result or calls super.next(param) to continue the chain.

Configuration Class

public class ProductCheckHandlerConfig {
    private String handler;          // bean name of the handler
    private ProductCheckHandlerConfig next; // next node
    private Boolean down = Boolean.FALSE;   // optional downgrade flag
}

The configuration is stored as a JSON string (simulating a configuration centre) and parsed into ProductCheckHandlerConfig objects.

String configJson = "{\"handler\":\"nullValueCheckHandler\",\"down\":true,\"next\":{\"handler\":\"priceCheckHandler\",\"next\":{\"handler\":\"stockCheckHandler\",\"next\":null}}}";
ProductCheckHandlerConfig handlerConfig = JSON.parseObject(configJson, ProductCheckHandlerConfig.class);

Building the Chain

All handler beans are injected into a Map<String, AbstractCheckHandler> handlerMap via Spring @Resource. The method getHandler(ProductCheckHandlerConfig config) performs three steps:

Validate the configuration and retrieve the bean from handlerMap.

Assign the configuration object to the handler.

Recursively set the next handler using the same method.

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(getHandler(config.getNext()));
    return handler;
}

Client Execution

The static method HandlerClient.executeChain(handler, param) starts the chain. If any handler returns a failure, the client logs the error and aborts; otherwise it returns success.

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

Test Scenarios

Four test cases demonstrate the chain behavior:

Missing SKU triggers the null‑value handler and returns an error.

Negative price triggers the price handler.

Negative stock triggers the stock handler.

All validations pass and the product is saved.

Case 2: Workflow – Expense Reimbursement Approval

The same pattern can model a hierarchical approval workflow. Depending on the reimbursement amount, the request passes through three levels of approvers (team, department, senior manager). Each level is implemented as a concrete handler that overrides approve() and forwards to the next handler if the amount exceeds its limit.

The configuration (JSON) contains the approver name, minimum/maximum amount, and the next handler reference, allowing dynamic adjustment of approval thresholds.

Implementation follows the same steps as Case 1: build the chain from configuration, inject handlers via Spring, and invoke the chain through a client class.

Summary

By extracting validation or approval logic into independent handlers, the Chain of Responsibility pattern decouples business rules, enables dynamic reconfiguration, promotes reuse, and keeps the core service method clean. The article provides a complete, runnable example with configuration, bean injection, recursive chain assembly, and test cases, illustrating how to apply the pattern in real‑world Java backend projects.

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.

backendChain of ResponsibilityDesign PatternsJavaWorkflowSpringValidation
Programmer XiaoFu
Written by

Programmer XiaoFu

xiaofucode.com – a programmer learning guide driven by the pursuit of profit

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.