Backend Development 16 min read

Chain of Responsibility Pattern in Java with Strategy Pattern Integration

This article explains the Chain of Responsibility design pattern, its core concepts, characteristics, and problems it solves, then demonstrates how to combine it with the Strategy pattern in Java using Spring, complete with detailed code examples and execution flow.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Chain of Responsibility Pattern in Java with Strategy Pattern Integration

What Is the Chain of Responsibility Pattern?

Chain of Responsibility (CoR) Pattern is a behavioral design pattern that allows a request to travel along a chain of handlers until one of them processes it, decoupling the sender from the concrete handler.

Core Idea: Decouple request sender and receiver by linking multiple objects into a chain, passing the request along until it is handled.

Characteristics of the Chain of Responsibility Pattern

Decouples sender and handler: The sender does not need to know the concrete handler, enhancing flexibility and extensibility.

Dynamic composition of logic: The chain structure can be changed at runtime by adding or removing handlers.

Single responsibility: Each handler encapsulates a single validation or processing step, adhering to the Single Responsibility Principle.

Extensibility: New validation logic can be added by implementing a common interface without modifying existing code.

Clear workflow: Organizes all validation logic in a linear, easy‑to‑understand sequence.

Combining Chain of Responsibility with Strategy Pattern

CoR role: Dynamically chain and forward requests.

Strategy role: Encapsulate a set of algorithms that can be selected at runtime.

By combining the two, a chain can be built dynamically while each handler delegates its specific processing to a chosen strategy, achieving both flexible flow control and interchangeable business logic.

Problems Solved by the Chain of Responsibility

High coupling: Separates request senders from handlers, allowing independent extension.

Complex multi‑condition checks: Avoids excessive if‑else or switch‑case statements.

Lack of flexibility: Dynamic chain composition lets you adjust request flow or insert new handlers easily.

Code duplication: Each handler focuses on its own concern, reducing repeated code.

Code Walkthrough – Chain of Responsibility Implementation

Scenario 1: Product Shelf Logic (Multiple Validations)

Step 1 – Define the abstract chain interface:

public interface MerchantAdminAbstractChainHandler<T> extends Ordered {
    /** Execute chain logic */
    void handler(T requestParam);
    /** Identify the chain */
    String mark();
}

Step 2 – Define chain identifiers:

public enum ChainBizMarkEnum {
    MERCHANT_ADMIN_CREATE_PRODUCT_TEMPLATE_KEY,
    MERCHANT_ADMIN_PRODUCT_UPSHELF_KEY; // New product shelf chain identifier
}

Step 3 – Implement the chain context that collects handlers from Spring and orders them:

@Component
public final class MerchantAdminChainContext<T> implements ApplicationContextAware, CommandLineRunner {
    private ApplicationContext applicationContext;
    private final Map<String, List<MerchantAdminAbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();

    public void handler(String mark, T requestObj) {
        List<MerchantAdminAbstractChainHandler> handlers = abstractChainHandlerContainer.get(mark);
        if (CollectionUtils.isEmpty(handlers)) {
            throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));
        }
        handlers.forEach(each -> each.handler(requestObj));
    }

    @Override
    public void run(String... args) throws Exception {
        Map<String, MerchantAdminAbstractChainHandler> chainFilterMap = applicationContext.getBeansOfType(MerchantAdminAbstractChainHandler.class);
        chainFilterMap.forEach((beanName, bean) -> {
            List<MerchantAdminAbstractChainHandler> list = abstractChainHandlerContainer.getOrDefault(bean.mark(), new ArrayList<>());
            list.add(bean);
            abstractChainHandlerContainer.put(bean.mark(), list);
        });
        abstractChainHandlerContainer.forEach((mark, chainHandlers) -> chainHandlers.sort(Comparator.comparing(Ordered::getOrder)));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

Step 4 – Implement concrete handlers for product information validation and inventory check:

@Component
public class ProductInfoNotNullChainFilter implements MerchantAdminAbstractChainHandler<ProductUpShelfReqDTO> {
    @Override
    public void handler(ProductUpShelfReqDTO requestParam) {
        if (StringUtils.isEmpty(requestParam.getProductName())) {
            throw new RuntimeException("Product name cannot be empty!");
        }
        if (requestParam.getPrice() == null || requestParam.getPrice() <= 0) {
            throw new RuntimeException("Product price must be greater than 0!");
        }
        System.out.println("Product info validation passed");
    }
    @Override
    public int getOrder() { return 1; }
    @Override
    public String mark() { return ChainBizMarkEnum.MERCHANT_ADMIN_PRODUCT_UPSHELF_KEY.name(); }
}

@Component
public class ProductInventoryCheckChainFilter implements MerchantAdminAbstractChainHandler<ProductUpShelfReqDTO> {
    @Override
    public void handler(ProductUpShelfReqDTO requestParam) {
        if (requestParam.getStock() <= 0) {
            throw new RuntimeException("Insufficient stock, cannot shelf product!");
        }
        System.out.println("Product inventory validation passed");
    }
    @Override
    public int getOrder() { return 2; }
    @Override
    public String mark() { return ChainBizMarkEnum.MERCHANT_ADMIN_PRODUCT_UPSHELF_KEY.name(); }
}

Step 5 – Use the chain in a service layer:

@Service
@RequiredArgsConstructor
public class ProductServiceImpl {
    private final MerchantAdminChainContext<ProductUpShelfReqDTO> merchantAdminChainContext;

    public void upShelfProduct(ProductUpShelfReqDTO requestParam) {
        merchantAdminChainContext.handler(ChainBizMarkEnum.MERCHANT_ADMIN_PRODUCT_UPSHELF_KEY.name(), requestParam);
        System.out.println("Product shelf logic starts executing...");
        // Further business logic
    }
}

Java Implementation of Chain of Responsibility + Strategy Pattern

The following example demonstrates a combined CoR and Strategy solution for a user‑request approval workflow.

1. Define the request handler interface (CoR)

public interface RequestHandler {
    void setNextHandler(RequestHandler nextHandler);
    void handleRequest(UserRequest request);
}

2. Define the user request model

public class UserRequest {
    private String userType; // e.g., basic, admin, super admin
    private String requestContent;
    public UserRequest(String userType, String requestContent) { this.userType = userType; this.requestContent = requestContent; }
    public String getUserType() { return userType; }
    public String getRequestContent() { return requestContent; }
}

3. Define strategy interface and concrete strategies

public interface RequestStrategy { void process(UserRequest request); }

public class BasicUserStrategy implements RequestStrategy { @Override public void process(UserRequest request) { System.out.println("Basic user request: " + request.getRequestContent()); } }

public class AdminUserStrategy implements RequestStrategy { @Override public void process(UserRequest request) { System.out.println("Admin request: " + request.getRequestContent()); } }

public class SuperAdminStrategy implements RequestStrategy { @Override public void process(UserRequest request) { System.out.println("Super admin request: " + request.getRequestContent()); } }

4. Implement the CoR handler that delegates to a strategy

public class RequestHandlerImpl implements RequestHandler {
    private RequestStrategy strategy;
    private RequestHandler nextHandler;
    public RequestHandlerImpl(RequestStrategy strategy) { this.strategy = strategy; }
    @Override public void setNextHandler(RequestHandler nextHandler) { this.nextHandler = nextHandler; }
    @Override public void handleRequest(UserRequest request) {
        strategy.process(request);
        if (nextHandler != null) { nextHandler.handleRequest(request); }
    }
}

5. Test the combined pattern

public class ChainStrategyExample {
    public static void main(String[] args) {
        RequestStrategy basic = new BasicUserStrategy();
        RequestStrategy admin = new AdminUserStrategy();
        RequestStrategy superAdmin = new SuperAdminStrategy();

        RequestHandler basicHandler = new RequestHandlerImpl(basic);
        RequestHandler adminHandler = new RequestHandlerImpl(admin);
        RequestHandler superAdminHandler = new RequestHandlerImpl(superAdmin);

        basicHandler.setNextHandler(adminHandler);
        adminHandler.setNextHandler(superAdminHandler);

        UserRequest basicReq = new UserRequest("Basic", "Access resource A");
        UserRequest adminReq = new UserRequest("Admin", "Modify resource B");
        UserRequest superReq = new UserRequest("SuperAdmin", "Delete resource C");

        System.out.println("Processing basic user request:");
        basicHandler.handleRequest(basicReq);
        System.out.println("\nProcessing admin request:");
        adminHandler.handleRequest(adminReq);
        System.out.println("\nProcessing super admin request:");
        superAdminHandler.handleRequest(superReq);
    }
}

Why Combine Chain of Responsibility and Strategy?

Chain controls flow, strategy defines processing: The chain manages request propagation while strategies encapsulate the actual business logic, allowing dynamic adjustments.

Separation of concerns: CoR handles routing, Strategy handles concrete actions, resulting in clearer code structure.

Enhanced flexibility and extensibility: New handlers or strategies can be added without affecting existing components, supporting evolving business requirements.

Combining the Chain of Responsibility pattern with the Strategy pattern enables handling complex workflows and varying business needs while keeping the code concise and highly cohesive.
backendchain of responsibilitydesign patternsJavaStrategy PatternSpring
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

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.