Spring Boot Command Pattern Example
By Ercan - 21/10/2025
The Command Pattern is one of the most versatile and widely used behavioral design patterns. It encapsulates a request as an object, allowing you to parameterize, queue, and execute actions dynamically — all without tightly coupling the caller to the actual logic being executed.
In this article, we’ll explore how to apply the Command Pattern in a real-world Spring Boot project, where we execute different text encoding and decoding algorithms such as Caesar, Atbash, and ROT13.
This example is designed to be both educational and practical — ideal for developers who want to deeply understand the pattern beyond theory.
TL;DR: If you want to skip the explanation and dive straight into the code, check out the full implementation on GitHub: https://github.com/ercansormaz/command-pattern
Core Idea of the Command Pattern
The Command Pattern decouples the invoker (the object that triggers an action) from the receiver (the object that performs it).
Each command encapsulates:
- The action to perform
- The receiver of that action
- The parameters needed to perform it
This separation provides flexibility for executing, batching, or even undoing operations without changing the invoker’s logic.
How It Works
1. Command Interface
Every command implements a common interface:
public interface CryptoCommand {
String execute();
}
2. Concrete Commands
Each algorithm has its own command class, e.g.:
@RequiredArgsConstructor
public class CaesarEncodeCryptoCommand implements CryptoCommand {
private final CaesarCipher chiper;
private final CryptoCommandInput input;
@Override
public String execute() {
return chiper.encode(input.getInput(), input.getShift());
}
}
3. Factories
To decouple instantiation, each algorithm has a factory:
@Component
@RequiredArgsConstructor
public class CaesarEncodeCryptoCommandFactory implements CryptoCommandFactory {
private final CaesarCipher caesarCipher;
@Override
public CryptoCommand create(CryptoCommandInput input) {
return new CaesarEncodeCryptoCommand(caesarCipher, input);
}
@Override
public CryptoMethod getMethod() {
return CryptoMethod.CAESAR;
}
@Override
public List<CryptoOperation> getSupportedOperations() {
return List.of(CryptoOperation.ENCODE);
}
}
4. Invoker
The CryptoCommandInvoker dynamically maps all factories at startup:
@PostConstruct
public void prepareCommandMap() {
cryptoCommandFactories.forEach(factory -> {
Map<CryptoOperation, CryptoCommandFactory> innerMap = commandFactoryMap.computeIfAbsent(
factory.getMethod(), m -> new EnumMap<>(CryptoOperation.class));
factory.getSupportedOperations().forEach(op -> innerMap.putIfAbsent(op, factory));
});
}
private CryptoCommand getCommand(CryptoCommandInput input) {
Map<CryptoOperation, CryptoCommandFactory> byMethod = commandFactoryMap.get(input.getMethod());
if (byMethod == null) {
throw new IllegalArgumentException("Unsupported method: " + input.getMethod());
}
CryptoCommandFactory factory = byMethod.get(input.getOperation());
if (factory == null) {
throw new IllegalArgumentException(
"Unsupported operation: " + input.getOperation() + " for method: " + input.getMethod());
}
return factory.create(input);
}
This removes all switch-case logic and keeps the system open for extension — just add a new factory and command class, and the invoker will pick it up automatically.
REST Endpoints
The project exposes three endpoints to showcase different command execution scenarios.
🔹 Single Command
POST /crypto
{
"operation": "encode",
"method": "caesar",
"text": "hello",
"shift": 3
}
Response:
{
"operation": "ENCODE",
"method": "CAESAR",
"result": "khoor",
"shift": 3
}
🔹 Batch Commands
POST /crypto/batch
{
"commands": [
{"operation":"encode", "method":"caesar", "text":"hello", "shift":3},
{"operation":"encode", "method":"rot13", "text":"hello"},
{"operation":"encode", "method":"atbash", "text":"hello"}
]
}
Executes multiple commands sequentially and returns an array of results.
🔹 Pipeline Commands
POST /crypto/pipeline
{
"text": "hello world",
"commands": [
{"operation":"encode", "method":"caesar", "shift":3},
{"operation":"encode", "method":"rot13"},
{"operation":"encode", "method":"atbash"}
]
}
Each command’s output becomes the next command’s input — a great example of command chaining.
What Makes This Example Stand Out
- Decoupled Design: The controller, invoker, and commands operate independently.
- Extensibility: Adding a new algorithm only requires a new command + factory.
- Batch & Pipeline Execution: Demonstrates real-world flexibility of the Command pattern.
- Spring Integration: Uses Spring components for discovery and runtime mapping.
Takeaways
This project shows how the Command Pattern can elegantly handle dynamic operations — whether they’re single, grouped, or chained.
It’s not just a theoretical example, but a practical demonstration of how design patterns improve scalability and clarity in modern Spring Boot applications.
👉 You can explore the full source code on GitHub:
https://github.com/ercansormaz/command-pattern
Tags: spring boot, design pattern, command pattern, java
