📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
🎓 Top 15 Udemy Courses (80-90% Discount): My Udemy Courses - Ramesh Fadatare — All my Udemy courses are real-time and project oriented courses.
▶️ Subscribe to My YouTube Channel (176K+ subscribers): Java Guides on YouTube
▶️ For AI, ChatGPT, Web, Tech, and Generative AI, subscribe to another channel: Ramesh Fadatare on YouTube
When building large backend systems in Java, especially with Spring Boot, you often need to handle operations that are triggered by external requests — such as:
- Creating an order
- Sending a notification
- Processing a refund
- Generating a report
As these operations grow in number and complexity, your code can become hard to manage — with long if-else
or switch
statements and tightly coupled service methods.
That’s where the Command Pattern comes in.
In this article, we’ll cover:
- What the Command Pattern is
- Why and when to use it in real Spring Boot projects
- A complete example: handling different types of user actions
- How to implement it step-by-step
- Best practices and when to avoid it
Let’s keep it practical.
What is the Command Pattern?
The Command Pattern is a behavioral design pattern that turns a request into a standalone object, which contains all the information about the request and knows how to execute it.
In simple terms: each action becomes its own class with a method like
execute()
.
This makes it easier to:
- Add new actions
- Undo or log actions
- Queue or schedule actions
- Separate invoker (caller) from executor (logic)
Real-World Example: Handling User Actions in a Task Manager
Scenario:
Imagine a task management app. Users can perform different actions:
create-task
delete-task
complete-task
Each action has different logic, parameters, and validation.
Using the Command Pattern, we’ll create a clean structure where:
- Each command is handled by its own class
- The controller only knows how to pass the request
- The system is easy to extend (add new commands)
✅ Step 1: Define a Common Command Interface
Start with a simple interface that all commands will implement.
public interface Command {
void execute(UserActionRequest request);
}
The UserActionRequest
contains the request details.
✅ Step 2: Create the Request DTO
import java.util.Map;
public class UserActionRequest {
private String action;
private Map<String, String> data;
// Getters and setters
}
Example request body:
{
"action": "create-task",
"data": {
"title": "Finish blog article",
"description": "Write about Command Pattern"
}
}
✅ Step 3: Create Individual Command Handlers
CreateTaskCommand
import org.springframework.stereotype.Component;
@Component("create-task")
public class CreateTaskCommand implements Command {
@Override
public void execute(UserActionRequest request) {
String title = request.getData().get("title");
String description = request.getData().get("description");
System.out.println("Creating task: " + title + " - " + description);
// Real logic: save to database
}
}
DeleteTaskCommand
import org.springframework.stereotype.Component;
@Component("delete-task")
public class DeleteTaskCommand implements Command {
@Override
public void execute(UserActionRequest request) {
String taskId = request.getData().get("taskId");
System.out.println("Deleting task with ID: " + taskId);
// Real logic: remove from database
}
}
CompleteTaskCommand
import org.springframework.stereotype.Component;
@Component("complete-task")
public class CompleteTaskCommand implements Command {
@Override
public void execute(UserActionRequest request) {
String taskId = request.getData().get("taskId");
System.out.println("Completing task with ID: " + taskId);
// Real logic: update task status
}
}
✅ Step 4: Create the Command Factory (or Dispatcher)
This class selects the correct command based on the action
field.
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class CommandDispatcher {
private final Map<String, Command> commandMap;
public CommandDispatcher(Map<String, Command> commandMap) {
this.commandMap = commandMap;
}
public void dispatch(UserActionRequest request) {
Command command = commandMap.get(request.getAction());
if (command == null) {
throw new IllegalArgumentException("Unknown action: " + request.getAction());
}
command.execute(request);
}
}
Because we used @Component("action-name")
, Spring injects all command beans into the Map<String, Command>
using their bean names as keys.
✅ Step 5: Create the REST Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/user-actions")
public class UserActionController {
@Autowired
private CommandDispatcher dispatcher;
@PostMapping
public ResponseEntity<String> handleAction(@RequestBody UserActionRequest request) {
dispatcher.dispatch(request);
return ResponseEntity.ok("Action '" + request.getAction() + "' executed successfully.");
}
}
You now have a single endpoint that can handle many actions — each handled cleanly in its own class.
🧪 Test It
Create a task:
POST /api/user-actions
{
"action": "create-task",
"data": {
"title": "Write article",
"description": "Explain Command Pattern"
}
}
Console output:
Creating task: Write article - Explain Command Pattern
Complete a task:
{
"action": "complete-task",
"data": {
"taskId": "123"
}
}
Console:
Completing task with ID: 123
✅ The controller never changes. Adding new actions just means adding a new command class.
Why Use Command Pattern in Spring Boot?
Problem | Solution with Command Pattern |
---|---|
Complex switch or if-else logic |
Each action becomes a separate command class |
Hard to test | Each command can be unit-tested independently |
Difficult to extend | New actions are added without changing core logic |
Cluttered controller | Controller stays clean — no business logic |
Other Use Cases for the Command Pattern
Use Case | Description |
---|---|
Admin panels | Ban user, reset password, grant roles |
Order processing | Confirm order, cancel order, issue refund |
File management | Upload, rename, delete, compress files |
Workflow engines | Each workflow step is a command |
Scheduled jobs | Each job = command to execute |
✅ Best Practices
- Use meaningful bean names – they map to action types
- Keep commands focused – one action per class
- Use a dispatcher or registry – don’t hardcode command selection
- Log failures properly – wrap
command.execute()
in try-catch if needed - Add unit tests per command – each one should be independently testable
⚠️ When Not to Use Command Pattern
Avoid the Command Pattern if:
- You only have 1–2 actions — a service class may be enough
- You don’t expect new actions to be added
- Simplicity is more important than structure
Remember: patterns add value when your code needs flexibility and clean separation.
Summary
Step | What We Did |
---|---|
1 | Defined a Command interface with execute() |
2 | Created request class to hold action + data |
3 | Wrote separate command classes for each action |
4 | Used Spring to wire commands into a map |
5 | Created a dispatcher to pick and run the right command |
6 | Kept controller clean and extensible |
Each action is now isolated, testable, and easy to maintain — without cluttering the controller.
Final Thoughts
The Command Pattern is a great fit when your backend app handles many different user-triggered operations. Instead of stuffing logic into your controller or a massive service class, you split each action into its own command — making your codebase easier to test, debug, and extend.
Spring Boot makes this pattern even easier with component scanning and map-based injection. Just define your command, annotate it, and you're done.
When your codebase starts feeling like a big switch statement — it’s time to switch to the Command Pattern.
Comments
Post a Comment
Leave Comment