Mastering the Chain of Responsibility Pattern in Java: A Practical Guide
This article explains the Chain of Responsibility design pattern, outlines its four roles, demonstrates a real‑world Java implementation for encrypted request handling, and shows how to build an extensible processing chain that improves code readability and maintainability.
1. Introduction
The Chain of Responsibility pattern creates a processing chain between a requester and a receiver, decoupling the sender from the handler . It involves four roles: request, abstract handler, concrete handler, and next handler.
2. Example
In many projects, request parameters are encrypted before being sent over the network. The following example shows how to use the Chain of Responsibility pattern to decrypt, validate, and encapsulate request data.
2.1 AES Utility
public class AESUtil {
private static Logger log = LoggerFactory.getLogger(AESUtil.class);
private static final String AES = "AES";
private static final String AES_CVC_PKC = "AES/CBC/PKCS7Padding";
static { Security.addProvider(new BouncyCastleProvider()); }
/**
* Encrypt
* @param content
* @param key
* @return
* @throws Exception
*/
public static String encrypt(String content, String key) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), AES);
Cipher cipher = Cipher.getInstance(AES_CVC_PKC);
IvParameterSpec iv = new IvParameterSpec(new byte[16]);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
byte[] encrypted = cipher.doFinal(content.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
log.warn("AES encryption failed, param:{}, error:{}", content, ExceptionUtils.getStackTrace(e));
return "";
}
}
/**
* Decrypt
* @param content
* @param key
* @return
* @throws Exception
*/
public static String decrypt(String content, String key) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), AES);
Cipher cipher = Cipher.getInstance(AES_CVC_PKC);
IvParameterSpec iv = new IvParameterSpec(new byte[16]);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
byte[] encrypted = Base64.getDecoder().decode(content);
byte[] original = cipher.doFinal(encrypted);
return new String(original, "UTF-8");
} catch (Exception e) {
log.warn("AES decryption failed, param:{}, error:{}", content, ExceptionUtils.getStackTrace(e));
return "";
}
}
public static void main(String[] args) throws Exception {
String key = "1234567890123456";
String content = "{\"userCode\":\"zhangsan\",\"userPwd\":\"123456\"}";
String encryptContext = encrypt(content, key);
System.out.println("Encrypted: " + encryptContext);
String decryptContext = decrypt(encryptContext, key);
System.out.println("Decrypted: " + decryptContext);
}
}Running the above prints the encrypted string and its decrypted JSON representation.
2.2 Context Entity
/**
* Context
*/
public class ServiceContext {
private String requestParam;
private String jsonData;
private String userCode;
private String userPwd;
// getters and setters omitted
public ServiceContext() {}
public ServiceContext(String requestParam) { this.requestParam = requestParam; }
}2.3 Handler Interface
public interface HandleIntercept {
/**
* Process the context
*/
ServiceContext handle(ServiceContext context);
}2.4 Concrete Handlers
/** Decrypt request data */
public class DecodeDataHandle implements HandleIntercept {
private String key = "1234567890123456";
@Override
public ServiceContext handle(ServiceContext context) {
String jsonData = AESUtil.decrypt(context.getRequestParam(), key);
if (StringUtils.isEmpty(jsonData)) {
throw new IllegalArgumentException("Decryption failed");
}
context.setJsonData(jsonData);
return context;
}
}
/** Validate business data and encapsulate */
public class ValidDataHandle implements HandleIntercept {
@Override
public ServiceContext handle(ServiceContext context) {
String jsonData = context.getJsonData();
JSONObject jsonObject = JSONObject.parseObject(jsonData);
if (!jsonObject.containsKey("userCode")) {
throw new IllegalArgumentException("userCode cannot be empty");
}
context.setUserCode(jsonObject.getString("userCode"));
if (!jsonObject.containsKey("userPwd")) {
throw new IllegalArgumentException("userPwd cannot be empty");
}
context.setUserPwd(jsonObject.getString("userPwd"));
return context;
}
}2.5 Chain Manager
/** Request processing chain manager */
public class HandleChain {
private List<HandleIntercept> handleInterceptList = new ArrayList<>();
public void addHandle(HandleIntercept handleIntercept) {
handleInterceptList.add(handleIntercept);
}
public ServiceContext execute(ServiceContext context) {
for (HandleIntercept handle : handleInterceptList) {
context = handle.handle(context);
}
return context;
}
}2.6 Test Client
public class ChainClientTest {
public static void main(String[] args) {
String requestParam = "5ELORDsYKxCz6Ec377udct7dBMI74ZtJDCFL4B3cpoBsPC8ILH/aiaRFnZa/oTC5";
ServiceContext serviceContext = new ServiceContext(requestParam);
HandleChain handleChain = new HandleChain();
handleChain.addHandle(new DecodeDataHandle()); // decryption
handleChain.addHandle(new ValidDataHandle()); // validation
serviceContext = handleChain.execute(serviceContext);
System.out.println("Result: " + JSONObject.toJSONString(serviceContext));
}
}Execution output shows the encrypted request, the decrypted JSON, and the populated fields in ServiceContext:
Result:{"jsonData":"{\"userCode\":\"zhangsan\",\"userPwd\":\"123456\"}","requestParam":"5ELORDsYKxCz6Ec377udct7dBMI74ZtJDCFL4B3cpoBsPC8ILH/aiaRFnZa/oTC5","userCode":"zhangsan","userPwd":"123456"}The chain cleanly transforms the encrypted request into a fully populated context object, demonstrating superior readability and extensibility compared to nested if statements.
3. Application
The pattern is widely used in servlet filters. Each filter is a handler in the chain, registered in web.xml or via annotations, allowing modular processing of HTTP requests.
public class TestFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
public void destroy() {}
public void init(FilterConfig filterConfig) throws ServletException {}
}4. Conclusion
Use the Chain of Responsibility pattern when multiple conditional checks share a common abstraction, such as encryption, validation, or business rule processing. It yields elegant, reusable, and easily extensible code, avoiding deep nesting and improving maintainability.
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.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
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.
