In this article, I'm showing you how to apply the Chain Of Responsibility pattern in your Spring application smoothly. You can find the source code in this GitHub repository.
Domain
Let's clarify the CoR pattern purpose. You can read its entire explanation by this link. Though now I'm sharing with you one particular example.
Imagine that we're creating a real-time enrichment service. It consumes a message, fulfils it with additional data, and produces the final enriched message as the output. Supposing we have 3 types of enrichment:
- Phone number determination by
SESSIONIDcookie. - The person's age retrieving by the
userIdfield. - The person's geolocation obtaining by their IP address.
Development
Firstly, we need the EnrichmentStep interface.
public interface EnrichmentStep {
Message enrich(Message msg);
}
The interface accepts the current Message and returns the enriched one.
In this case, the
Messageclass is immutable. Meaning thatEnrichmentStepreturns the new object but not the same one that is modified. Immutable classes usage is good practice because it eliminates lots of possible concurrency problems.
There are 3 types of enrichment. Therefore, we need 3 EnrichmentStep implementations. I'm showing you the phone number example. Though if you're curious, you can see others in the repository.
@Service
public class PhoneNumberEnrichmentStep implements EnrichmentStep {
private final PhoneNumberRepository phoneNumberRepository;
@Override
public Message enrich(Message message) {
return message.getValue("SESSIONID")
.flatMap(phoneNumberRepository::findPhoneNumber)
.map(phoneNumber -> message.with("phoneNumber", phoneNumber))
.orElse(message);
}
}
In this case, we don't care about the
PhoneNumberRepositoryimplementation.
OK then. Each EnrichmentStep might enhance the input message with additional data. But we need to proceed with all the enrichment steps to obtain the fulfilled message. The Chain of Responsibility pattern comes in handy. Let's rewrite the EnrichmentStep interface a bit.
public interface EnrichmentStep {
Message enrich(Message message);
void setNext(EnrichmentStep step);
}
Each EnrichmentStep implementation might reference the next chain element. Take a look at the modified PhoneNumberEnrichmentStep implementation.
@Service
public class PhoneNumberEnrichmentStep implements EnrichmentStep {
private final PhoneNumberRepository phoneNumberRepository;
private EnrichmentStep next;
@Override
public Message enrich(Message message) {
return message.getValue("SESSIONID")
.flatMap(phoneNumberRepository::findPhoneNumber)
.map(phoneNumber -> next.enrich(
message.with("phoneNumber", phoneNumber)
))
.orElseGet(() -> next.enrich(message));
}
public void setNext(EnrichmentStep step) {
this.next = step;
}
}
Now the PhoneNumberEnrichmentStep works a bit differently:
- If the message is successfully enriched, the fulfilled result proceeds to the next enrichment step.
- Otherwise the message with no modifications goes further.
It's time to connect EnrichmentStep implementations into a linked list, i.e. build the Chain of Responsibility.
First of all, let's point out another essential detail. You see, the EnrichmentStep defines that there is always the next step. But a chain cannot be infinite. Therefore, there is a possibility that the next chain element might be absent. In this case, we have to repeat not-null checks in every EnrichmentStep. Because any implementation might be the last step. Thankfully there is a better alternative. The NoOpEnrichmentStep is the implementation that just returns the same message without any actions. Take a look at the code block below.
public class NoOpEnrichmentStep implements EnrichmentStep {
@Override
public Message enrich(Message message) {
return message;
}
@Override
public void setNext(EnrichmentStep step) {
// no op
}
}
It allows us to set this object as the last chain element. So, it guarantees that the setNext method is always invoked with some value and we don't have to repeat not-null checks.
The
NoOpEnrichmentStepis actually an example of the Null Object Design pattern.
Now we're creating the EnrichmentStepFacade. Take a look at the code snippet below.
@Service
public class EnrichmentStepFacade {
private final EnrichmentStep chainHead;
public EnrichmentStepFacade(List<EnrichmentStep> steps) {
if (steps.isEmpty()) {
chainHead = new NoOpEnrichmentStep();
} else {
for (int i = 0; i < steps.size(); i++) {
var current = steps.get(i);
var next = i < steps.size() - 1 ? steps.get(i + 1) : new NoOpEnrichmentStep();
current.setNext(next);
}
chainHead = steps.get(0);
}
}
public Message enrich(Message message) {
return chainHead.enrich(message);
}
}
The constructor accepts a list of all EnrichmentStep implementations that are registered as Spring beans in the current application context (the framework does this automatically). If the list is empty, then the chainHead is just the NoOpEnrichmentStep instance. Otherwise, the current element is linked to the next one. But the last chain element always references NoOpEnrichmentStep. Meaning that calling the first element of the provided list will execute the whole chain! What's even more exciting is that you can define the order of elements in the chain just by putting the Spring @Order annotation. The injected List<EnrichmentStep> collection will be sorted accordingly.
Refactoring
The generic chain element
Though the solution is working it's not complete yet. There are a few details to improve. Firstly, take a look at the EnrichmentStep definition again.
public interface EnrichmentStep {
Message enrich(Message message);
void setNext(EnrichmentStep step);
}
The Chain of Responsibility is the generic pattern. Perhaps we'd apply it to another scenario. So, let's extract the setNext method to the separate interface. Take a look at the definition below.
public interface ChainElement<T> {
void setNext(T step);
}
And now the EnrichmentStep should extend it with the appropriate generic value.
public interface EnrichmentStep extends ChainElement<EnrichmentStep> {
Message enrich(Message message);
}
Chain building encapsulation
That's a slight improvement. What else can we do? Take a look at the EnrichmentStepFacade definition down below again.
@Service
public class EnrichmentStepFacade {
private final EnrichmentStep chainHead;
public EnrichmentStepFacade(List<EnrichmentStep> steps) {
if (steps.isEmpty()) {
chainHead = new NoOpEnrichmentStep();
} else {
for (int i = 0; i < steps.size(); i++) {
var current = steps.get(i);
var next = i < steps.size() - 1 ? steps.get(i + 1) : new NoOpEnrichmentStep();
current.setNext(next);
}
chainHead = steps.get(0);
}
}
public Message enrich(Message message) {
return chainHead.enrich(message);
}
}
As a matter of fact, the EnrichmentStep interface represents a generic chain element. So, we can encapsulate the code inside the ChainElement interface directly. Check out the code snippet below.
public interface ChainElement<T> {
void setNext(T step);
static <T extends ChainElement<T>> T buildChain(List<T> elements, T lastElement) {
if (elements.isEmpty()) {
return lastElement;
}
for (int i = 0; i < elements.size(); i++) {
var current = elements.get(i);
var next = i < elements.size() - 1 ? elements.get(i + 1) : lastElement;
current.setNext(next);
}
return elements.get(0);
}
}
The buildChain method accepts a list of business implementations that proceed with the actual use case and the stub one as the last element (i.e. NoOpEnrichmentStep).
Now we can refactor the EnrichmentStepFacade as well. Take a look at the code example below.
@Service
public class EnrichmentStepFacade {
private final EnrichmentStep chainHead;
public EnrichmentStepFacade(List<EnrichmentStep> steps) {
this.chainHead = ChainElement.buildChain(steps, new NoOpEnrichmentStep());
}
public Message enrich(Message message) {
return chainHead.enrich(message);
}
}
Much clearer and easier to understand.
AbstractEnrichmentStep
Anyway, there are still some caveats about the EnrichmentStep implementation. Take a look at the PhoneNumberEnrichmentStep definition below.
@Service
class PhoneNumberEnrichmentStep implements EnrichmentStep {
private final PhoneNumberRepository phoneNumberRepository;
private EnrichmentStep next;
@Override
public Message enrich(Message message) {
return message.getValue("SESSIONID")
.flatMap(phoneNumberRepository::findPhoneNumber)
.map(phoneNumber -> next.enrich(
message.with("phoneNumber", phoneNumber)
))
.orElseGet(() -> next.enrich(message));
}
public void setNext(EnrichmentStep step) {
this.next = step;
}
}
There are 2 details I want to point out:
- The
setNextmethod overriding. Each implementation has to store the reference to the next chain element. - The
next.enrich(...)method is called 2 times. So, the implementation has to repeat the contract requirements over and over again.
To eliminate these code smells, we declare the AbstractEnrichmentStep class. Take a look at the code snippet below.
public abstract class AbstractEnrichmentStep implements EnrichmentStep {
private EnrichmentStep next;
@Override
public final void setNext(EnrichmentStep step) {
this.next = step;
}
@Override
public final Message enrich(Message message) {
try {
return enrichAndApplyNext(message)
.map(enrichedMessage -> next.enrich(enrichedMessage))
.orElseGet(() -> next.enrich(message));
}
catch (Exception e) {
log.error("Unexpected error during enrichment for msg {}", message, e);
return next.enrich(message);
}
}
protected abstract Optional<Message> enrichAndApplyNext(Message message);
}
Firstly, the next EnrichmentStep is encapsulated within the AbstractEnrichmentStep and the setNext method is final. So, implementations don't bother about storing the further chain element.
Secondly, there is the new method enrichAndApplyNext. As a matter of fact, an implementation doesn't have to worry about chaining nuances at all. If enrichment is successful, then the method returns the new message. Otherwise, Optional.empty is retrieved.
And finally, the enrich method is also final. Therefore, its implementation is fixed. As you can see, we enrichment algorithm is not duplicated across multiple classes but placed within a single method. It is simple:
- If
enrichAndApplyNextreturns the value, proceed it to the next enrichment step. - If no value is present, invoke the next step with the origin message.
- If any error occurs, log it and continue the enrichment chain further normally.
The last point is crucial. We don't know how many implementations there will be and what exceptions they may throw. Nevertheless, we don't want to stop the enrichment process entirely but just skip the failed chain block execution. Take a look at the PhoneNumberEnrichmentRepository implementation below that extends the defined AbstractEnrichmentStep.
@Service
class PhoneNumberEnrichmentStep extends AbstractEnrichmentStep {
private final PhoneNumberRepository phoneNumberRepository;
@Override
protected Optional<Message> enrichAndApplyNext(Message message) {
return message.getValue("SESSIONID")
.flatMap(phoneNumberRepository::findPhoneNumber)
.map(phoneNumber ->
message.with("phoneNumber", phoneNumber)
);
}
}
As you see, there is no infrastructure code anymore. Just pure business logic.
Points of improvement
If you have many enrichment steps, then certainly you want to monitor their activity.
- What time it takes to enrich the message on the particular step?
- What are the enrichment statistics? Which enrichments steps hit and miss most frequently?
- Which steps fail and why?
Metrics is the answer to all of these questions. Besides, the AbstractEnrichmentStep declaration can also help us to record monitoring values clearer. Check out the code snippet down below.
public abstract class AbstractEnrichmentStep implements EnrichmentStep {
@Autowired
private MetricService metricService;
private EnrichmentStep next;
protected abstract EnrichmentType getEnrichmentType();
@Override
public final void setNext(EnrichmentStep step) {
this.next = step;
}
@Override
public final Message enrich(Message message) {
var start = System.nanoTime();
var type = getEnrichmentType();
try {
return enrichAndApplyNext(message)
.map(enrichedMessage -> {
metricService.recordHit(type);
return next.enrich(enrichedMessage);
})
.orElseGet(() -> {
metricService.recordMiss(type);
return next.enrich(message);
});
}
catch (Exception e) {
log.error("Unexpected error during enrichment for msg {}", message, e);
metricService.recordError(type, e);
return next.enrich(message);
}
finally {
var duration = Duration.ofNanos(System.nanoTime() - start);
metricService.recordDuration(type, duration);
}
}
protected abstract Optional<Message> enrichAndApplyNext(Message message);
}
First of all, there is a new abstract method getEnrichmentType(). Each implementation should return its type to distinguish result metrics correctly.
Then the rules are these:
- If the message is successfully enriched, the
recordHitmethod is called. - If the message enrichment is skipped, the
recordMissgoes. - If any error occurs, then the
recordErrorcomes into play. - Finally the whole enrichment step duration is stored by
recordDurationinvocation.
You can tune the metrics the way you want. The idea is that the implementations don't care about those details. The Open-closed principle in action!
Conclusion
That's all I wanted to tell you about the Chain of Responsibility implementation in the Spring ecosystem. If you have any questions or suggestions, please leave your comments down below. Thanks for reading!

Top comments (2)
Great article! The pattern with message passing between chains is well explained. I would like to know your opinion - wouldn't it be better to immediately use the Apache Camel framework, where CoR is declaratively described in the form of routes?
@dmitrychebykin Thanks for the feedback! You could definitely apply an existing framework or a library to implement the required pattern. At the same time, learning new technology and fitting it right in your project is also time consuming. So, if you're already using Apache Camel in the project, then applying it directly is a way to go.