Command Pattern in a Spring Boot Project

📘 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

  1. Use meaningful bean names – they map to action types
  2. Keep commands focused – one action per class
  3. Use a dispatcher or registry – don’t hardcode command selection
  4. Log failures properly – wrap command.execute() in try-catch if needed
  5. 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

Spring Boot 3 Paid Course Published for Free
on my Java Guides YouTube Channel

Subscribe to my YouTube Channel (165K+ subscribers):
Java Guides Channel

Top 10 My Udemy Courses with Huge Discount:
Udemy Courses - Ramesh Fadatare