🎮 Command Design Pattern
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
🌍 Real-World Analogy
Analogy — Restaurant Orders
In a restaurant, you don't walk into the kitchen and cook your food. Instead, the waiter takes your order (Command), writes it on a slip, and hands it to the chef (Receiver). The order slip is a tangible object that can be queued, prioritized, canceled, or even repeated. The waiter doesn't need to know how to cook — they just deliver commands.
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
You["🧑 You"] -->|"orders"| W["📝 Waiter"] -->|"writes"| O["🎫 Order Slip"] -->|"sends to"| C["👨🍳 Chef"] -->|"makes"| F["🍝 Food Ready"]
style You fill:#E8F5E9,stroke:#2E7D32,color:#000
style W fill:#FFF3E0,stroke:#E65100,color:#000
style O fill:#FFF8E1,stroke:#F9A825,stroke-width:2px,color:#000
style C fill:#E3F2FD,stroke:#1565C0,color:#000
style F fill:#FCE4EC,stroke:#C62828,color:#000 🏗️ Pattern Structure
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
Client["👤 Client"] -->|"creates"| CmdA{{"⚡ Command A"}}
Client -->|"creates"| CmdB{{"🔥 Command B"}}
Client -->|"configures"| Invoker["📋 Invoker"]
Invoker -->|"calls"| Command[["🎯 Command"]]
CmdA -->|"implements"| Command
CmdB -->|"implements"| Command
CmdA -.->|"acts on"| Receiver["🔧 Receiver"]
CmdB -.->|"acts on"| Receiver
style Client fill:#E8F5E9,stroke:#2E7D32,color:#000
style Invoker fill:#E3F2FD,stroke:#1565C0,color:#000
style Command fill:#FFF3E0,stroke:#E65100,color:#000
style CmdA fill:#E3F2FD,stroke:#1565C0,color:#000
style CmdB fill:#E3F2FD,stroke:#1565C0,color:#000
style Receiver fill:#FCE4EC,stroke:#C62828,color:#000 UML Class Diagram
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
classDiagram
class Command {
<<interface>>
+execute() void
+undo() void
+getDescription() String
}
class TextEditor {
-undoStack: Deque~Command~
-redoStack: Deque~Command~
+executeCommand(Command command) void
+undo() void
+redo() void
}
class TextDocument {
-content: StringBuilder
+insertText(int position, String text) void
+deleteText(int position, int length) void
+getContent() String
}
class InsertCommand {
-document: TextDocument
-position: int
-text: String
+execute() void
+undo() void
}
class DeleteCommand {
-document: TextDocument
-position: int
-length: int
-deletedText: String
+execute() void
+undo() void
}
class MacroCommand {
-commands: List~Command~
-name: String
+execute() void
+undo() void
}
TextEditor --> Command : invokes
InsertCommand ..|> Command
DeleteCommand ..|> Command
MacroCommand ..|> Command
MacroCommand o-- Command : contains
InsertCommand --> TextDocument : receiver
DeleteCommand --> TextDocument : receiver ❓ The Problem
You need to issue requests to objects without knowing:
- What operation is being requested
- Who the receiver of the request is
- When the operation should be executed
Additionally, you need:
- Undo/Redo capability
- Transaction logging — persist commands and replay them after a crash
- Macro commands — composite commands that execute a sequence
- Queueing — decouple when a request is made from when it's executed
Without This Pattern
Java
public class TextEditor {
private StringBuilder content = new StringBuilder();
public void insertText(int position, String text) {
content.insert(position, text);
// How do you undo this? You can't — state is already lost.
}
public void deleteText(int position, int length) {
content.delete(position, position + length);
// The deleted text is gone forever — no undo possible.
}
public void undo() {
// ??? No record of what was done, in what order, or what the previous state was.
throw new UnsupportedOperationException("Undo not possible");
}
}
- No undo/redo — operations directly mutate state without saving history; reverting is impossible
- Cannot queue or schedule operations — the action is executed immediately with no way to defer, serialize, or replay
- Tight coupling — the invoker (UI button) must know the exact method and receiver; you cannot parameterize actions
- No macro/batch support — you cannot group multiple operations into a single undoable unit
- Pain point: Users expect Ctrl+Z everywhere, but without encapsulating operations as objects, implementing undo requires fragile state snapshots of the entire document after every keystroke
✅ The Solution
The Command pattern turns a request into a stand-alone object containing all information about the request:
- Command interface — declares
execute()and optionallyundo() - Concrete Commands — implement the interface, hold a reference to the receiver
- Receiver — the object that performs the actual work
- Invoker — triggers command execution, maintains history for undo
- Client — creates commands and associates them with receivers
💻 Implementation
Java
// Command interface
public interface Command {
void execute();
void undo();
String getDescription();
}
// Receiver
public class TextDocument {
private final StringBuilder content = new StringBuilder();
public void insertText(int position, String text) {
content.insert(position, text);
}
public void deleteText(int position, int length) {
content.delete(position, position + length);
}
public String getContent() {
return content.toString();
}
}
// Concrete Commands
public class InsertCommand implements Command {
private final TextDocument document;
private final int position;
private final String text;
public InsertCommand(TextDocument document, int position, String text) {
this.document = document;
this.position = position;
this.text = text;
}
@Override
public void execute() {
document.insertText(position, text);
}
@Override
public void undo() {
document.deleteText(position, text.length());
}
@Override
public String getDescription() {
return "Insert '" + text + "' at position " + position;
}
}
public class DeleteCommand implements Command {
private final TextDocument document;
private final int position;
private final int length;
private String deletedText; // saved for undo
public DeleteCommand(TextDocument document, int position, int length) {
this.document = document;
this.position = position;
this.length = length;
}
@Override
public void execute() {
deletedText = document.getContent().substring(position, position + length);
document.deleteText(position, length);
}
@Override
public void undo() {
document.insertText(position, deletedText);
}
@Override
public String getDescription() {
return "Delete " + length + " chars at position " + position;
}
}
// Invoker with history
public class TextEditor {
private final Deque<Command> undoStack = new ArrayDeque<>();
private final Deque<Command> redoStack = new ArrayDeque<>();
public void executeCommand(Command command) {
command.execute();
undoStack.push(command);
redoStack.clear(); // Clear redo history on new action
}
public void undo() {
if (!undoStack.isEmpty()) {
Command command = undoStack.pop();
command.undo();
redoStack.push(command);
System.out.println("↩️ Undo: " + command.getDescription());
}
}
public void redo() {
if (!redoStack.isEmpty()) {
Command command = redoStack.pop();
command.execute();
undoStack.push(command);
System.out.println("↪️ Redo: " + command.getDescription());
}
}
}
// Usage
public class Main {
public static void main(String[] args) {
TextDocument doc = new TextDocument();
TextEditor editor = new TextEditor();
editor.executeCommand(new InsertCommand(doc, 0, "Hello "));
editor.executeCommand(new InsertCommand(doc, 6, "World!"));
System.out.println(doc.getContent()); // "Hello World!"
editor.undo(); // Removes "World!"
System.out.println(doc.getContent()); // "Hello "
editor.redo(); // Re-inserts "World!"
System.out.println(doc.getContent()); // "Hello World!"
}
}
Java
// Macro command — executes multiple commands as one
public class MacroCommand implements Command {
private final List<Command> commands;
private final String name;
public MacroCommand(String name, Command... commands) {
this.name = name;
this.commands = Arrays.asList(commands);
}
@Override
public void execute() {
commands.forEach(Command::execute);
}
@Override
public void undo() {
// Undo in reverse order
List<Command> reversed = new ArrayList<>(commands);
Collections.reverse(reversed);
reversed.forEach(Command::undo);
}
@Override
public String getDescription() {
return "Macro: " + name + " (" + commands.size() + " commands)";
}
}
// Usage — "Format Document" macro
TextDocument doc = new TextDocument();
TextEditor editor = new TextEditor();
editor.executeCommand(new InsertCommand(doc, 0, "Hello World"));
MacroCommand formatDoc = new MacroCommand("Format Document",
new InsertCommand(doc, 0, "=== HEADER ===\n"),
new InsertCommand(doc, doc.getContent().length(), "\n=== FOOTER ===")
);
editor.executeCommand(formatDoc);
editor.undo(); // Undoes entire macro at once
🎯 When to Use
- When you need undo/redo functionality
- When you want to queue operations and execute them at a different time
- When you need to support transactions (execute all or rollback)
- When you want to log changes so the system can recover by replaying commands
- When you need to parameterize objects with operations (callbacks on steroids)
- When you want to build macro commands (composite of multiple commands)
🏭 Real-World Examples
| Framework/Library | Usage |
|---|---|
java.lang.Runnable | A command with no receiver — just run() |
javax.swing.Action | UI actions encapsulated as command objects |
Spring @Transactional | Database operations as undoable commands |
Java Callable<V> | Command that returns a result |
| Apache Kafka | Messages are commands queued for async processing |
| CQRS Pattern | Commands separated from queries at architecture level |
java.util.concurrent.ThreadPoolExecutor | Commands (Runnable) queued for execution |
⚠️ Pitfalls
Common Mistakes
- Complexity overhead — Simple operations don't need to be commands. Use it when you actually need undo, queuing, or logging.
- Memory bloat — Storing unlimited command history can exhaust memory. Implement a max history size.
- Undo complexity — Some operations are hard to reverse (e.g., sending an email). Not every command is undoable.
- State capture — Commands must capture enough state at creation time to execute later. Be careful with references that may change.
- Thread safety — Shared command queues need synchronization.
📝 Key Takeaways
Summary
- Command decouples the object that invokes an operation from the one that performs it
- Enables undo/redo by storing execution history and implementing
undo()on each command - Commands are first-class objects — store them, pass them, serialize them
- Combine with Composite pattern for macro commands
- In Java,
RunnableandCallableare the simplest built-in command patterns - The pattern is the backbone of CQRS, Event Sourcing, and task scheduling architectures