Applying the Chain of Responsibility Pattern to Complex Data Validation in Java Servlets
The article demonstrates how to apply the Chain of Responsibility pattern to Java servlet filters by defining abstract and concrete validation filters, organizing them in an ordered FilterChain that uses ThreadLocal state (including composite filters) to replace tangled if‑else logic, improve extensibility, and warn of potential ThreadLocal memory‑leak risks.
The article explains how the Chain of Responsibility (CoR) design pattern can be used to handle complex data processing and validation scenarios in backend development, with a focus on Java servlet filters.
Concept
CoR allows multiple objects to have a chance to process the same request. The request sender and the handlers are decoupled, and the handlers are linked into a chain. Each handler decides whether to process the request and then passes it to the next handler.
Typical Application Scenario
When a single request requires several different validation or processing steps and the system needs high extensibility, CoR is a suitable solution.
Servlet Filters and FilterChain
In the servlet container, Filter and FilterChain are a classic example of CoR. A filter performs pre‑processing (e.g., authentication, logging) and then invokes chain.doFilter(request, response) to let the next filter continue.
public interface Filter {
// initialization
public void init(FilterConfig filterConfig) throws ServletException;
// processing logic
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
// destruction
public void destroy();
} public interface FilterChain {
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException;
}During a request, each filter in the chain processes the request and passes it forward. When the response is sent back, the filters are invoked in reverse order.
Why Not Use Plain If‑Else Code?
Using a long series of nested if‑else statements quickly becomes unreadable and hard to maintain, especially when new validation rules are added.
// Pseudo‑code without CoR
if (user selects product) {
if (modelA) { /* logic 1 */ }
else if (modelB) { /* logic 2 */ }
...
} else if (user selects store) {
if (storeTypeA) { /* logic */ }
...
}
// many more branches …This approach lacks modularity and violates the Single Responsibility Principle.
Improved Solution with CoR
Define an abstract filter that implements Filter and is comparable by order. Concrete filters implement their own validation logic and decide whether to handle a request via an accept method.
@Data
public abstract class AbstractOrderFilter implements Filter, Comparable<AbstractOrderFilter> {
protected Integer order;
@Override
public int compareTo(AbstractOrderFilter o) {
return getOrder().compareTo(o.getOrder());
}
public boolean accept(FilterRequestDTO filterRequestDTO) {
return true;
}
@Override
public void doFilter(FilterRequestDTO filterRequestDTO, FilterChain filterChain) {
// concrete filters override this
}
}Example concrete filter:
@Slf4j
public class ItemPermissionFilter extends AbstractOrderFilter {
ItemCheckManager itemCheckManager;
private ItemPermissionFilter(Integer order, ItemCheckManager itemCheckManager) {
super.order = order;
this.itemCheckManager = itemCheckManager;
}
@Override
public void doFilter(FilterRequestDTO filterRequestDTO, FilterChain filterChain) {
if (accept(filterRequestDTO)) {
itemCheckManager.checkItemPermission(filterRequestDTO, elementCheckResults);
}
filterChain.doFilter(filterRequestDTO);
}
@Override
public boolean accept(FilterRequestDTO filterRequestDTO) {
return true; // customize per business need
}
public static ItemPermissionFilter create(Integer order, ItemCheckManager mgr) {
return new ItemPermissionFilter(order, mgr);
}
}The chain implementation stores filters in order, uses a ThreadLocal index to track the current position, and aggregates validation results in another ThreadLocal list.
public class CouponFilterChain implements FilterChain {
private final List<? extends AbstractOrderFilter> filters;
private static ThreadLocal<Integer> posLocal = ThreadLocal.withInitial(() -> 0);
public static final ThreadLocal<List<CheckResult>> checkResult = new ThreadLocal<>();
private final int size;
@Override
public void doFilter(FilterRequestDTO filterRequestDTO) {
Integer pos = posLocal.get();
if (pos < size) {
pos++;
posLocal.set(pos);
Filter filter = this.filters.get(pos - 1);
filter.doFilter(filterRequestDTO, this);
}
}
public BaseResult<CheckResult> process(FilterRequestDTO dto) {
this.doFilter(dto);
return BaseResult.makeSuccess(checkResult.get());
}
@Override
public void reset() {
posLocal.remove();
posLocal.set(0);
checkResult.remove();
}
public CouponFilterChain(List<? extends AbstractOrderFilter> filters) {
filters.sort(AbstractOrderFilter::compareTo);
this.filters = filters;
this.size = filters.size();
}
}A manager class creates the chain by adding the required filters (e.g., ItemFilter, StoreFilter) and provides a process method for external callers.
@Component
@Slf4j
public class FilterChainManager {
@Resource StoreManager storeManager;
@Resource ItemManager itemManager;
private CouponFilterChain couponFilterChain;
@PostConstruct
private void init() {
List<AbstractOrderFilter> filters = new ArrayList<>();
filters.add(ItemFilter.create(100, ItemManager));
filters.add(StoreFilter.create(200, StoreManager));
this.couponFilterChain = new CouponFilterChain(filters);
}
public BaseResult<CheckResult> process(FilterRequestDTO dto) {
try {
return couponFilterChain.process(dto);
} catch (Exception e) {
return TMPResult.failOf("system error", e.getMessage());
} finally {
if (couponFilterChain != null) couponFilterChain.reset();
}
}
}Composite Filter Extension
For more complex scenarios, multiple filters can be grouped into a CompositeFilter, which itself behaves like a single filter but internally executes a sub‑chain before delegating to the outer chain.
public class CompositeFilter extends AbstractOrderFilter {
private List<? extends AbstractOrderFilter> filters = new ArrayList();
public CompositeFilter(Integer order, List<? extends AbstractOrderFilter> filters) {
super.order = order;
this.filters = filters;
}
@Override
public void doFilter(FilterRequestDTO dto, FilterChain chain) {
(new InnerFilterChain(chain, this.filters)).doFilter(dto);
}
private static class InnerFilterChain implements FilterChain {
private final FilterChain originalChain;
private final List<? extends AbstractOrderFilter> additionalFilters;
private int currentPosition = 0;
public InnerFilterChain(FilterChain chain, List<? extends AbstractOrderFilter> additionalFilters) {
this.originalChain = chain;
this.additionalFilters = additionalFilters;
}
@Override
public void doFilter(FilterRequestDTO dto) {
if (this.currentPosition >= this.additionalFilters.size()) {
this.originalChain.doFilter(dto);
} else {
this.currentPosition++;
AbstractOrderFilter current = this.additionalFilters.get(this.currentPosition - 1);
current.doFilter(dto, this);
}
}
@Override public void reset() {}
}
public static CompositeFilter create(Integer order, List<? extends AbstractOrderFilter> filters) {
filters.sort(AbstractOrderFilter::compareTo);
return new CompositeFilter(order, filters);
}
}The article concludes that the presented CoR implementation is a reference; developers should adapt the pattern to their own business contexts, be aware of ThreadLocal memory‑leak risks, and consider using simple List<Filter> structures when appropriate.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
