Applying the Chain of Responsibility Pattern for Multi‑Level Product Validation and Workflow in Java
This article explains the Chain of Responsibility design pattern, demonstrates its use for multi‑step product validation and a reimbursement workflow in Java with Spring, provides UML diagrams, configuration handling, concrete handler implementations, client execution code, and discusses the pattern’s advantages and drawbacks.
The Chain of Responsibility pattern assembles a series of processing nodes into a linked chain where each node (handler) can either process a request or pass it to the next handler.
Application Scenarios
Two typical scenarios are presented: (1) multi‑level validation when creating a product, and (2) a hierarchical approval workflow for expense reimbursement.
Case 1 – Product Multi‑Level Validation
The product creation flow consists of three steps: create product, validate parameters, and save product. Validation is split into several checks (null value, price, stock) that form a pipeline.
Pseudo‑code Overview
if (validation fails) return failure;
else save product;Using the pattern, each validation step becomes an independent handler class.
Abstract Handler
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);
}
}Configuration Class
public class ProductCheckHandlerConfig {
private String handler; // bean name
private ProductCheckHandlerConfig next;
private Boolean down = Boolean.FALSE;
}Concrete Handlers
@Component
public class NullValueCheckHandler extends AbstractCheckHandler {
@Override
public Result handle(ProductVO param) {
if (super.getConfig().getDown()) 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);
}
return super.next(param);
}
}
@Component
public class PriceCheckHandler extends AbstractCheckHandler {
@Override
public Result handle(ProductVO param) {
if (param.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
return Result.failure(ErrorCode.PARAM_PRICE_ILLEGAL_ERROR);
}
return super.next(param);
}
}
@Component
public class StockCheckHandler extends AbstractCheckHandler {
@Override
public Result handle(ProductVO param) {
if (param.getStock() < 0) {
return Result.failure(ErrorCode.PARAM_STOCK_ILLEGAL_ERROR);
}
return super.next(param);
}
}Handler Retrieval Logic
@Resource
private Map
handlerMap;
private AbstractCheckHandler getHandler(ProductCheckHandlerConfig config) {
if (config == null || StringUtils.isBlank(config.getHandler())) return null;
AbstractCheckHandler handler = handlerMap.get(config.getHandler());
if (handler == null) return null;
handler.setConfig(config);
handler.setNextHandler(getHandler(config.getNext()));
return handler;
}Client Execution
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();
}
}Test cases illustrate how missing fields, illegal price, or illegal stock abort the chain with appropriate error codes, while a fully valid product passes all handlers and is saved.
Case 2 – Reimbursement Workflow
A similar chain is used for an expense‑approval process where the amount determines how many management levels (third, second, first) must approve. Each level implements an approve() method and is linked via configuration, allowing dynamic adjustment of approval limits.
Advantages & Disadvantages
Advantages: decouples validation logic, improves reusability, and enables dynamic configuration. Disadvantages: can introduce complexity, especially with deep recursion, and may affect performance if the chain becomes very long.
Overall, the Chain of Responsibility pattern provides a clean way to separate concerns and build flexible, configurable processing pipelines in backend Java applications.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.