The command design pattern is a behavioral design pattern that wraps a request, such as “add this song to the playlist”, in an object with an execute() method. The code that triggers the request, such as a button or a scheduler, calls execute() and never needs to know which class does the work or how.
We use the command pattern when an app needs undo and redo, or when actions must wait in a queue and run later on another thread. Text editors and job schedulers are typical examples.
The following example runs three commands against a playlist through a small history object, and the comment after each line shows the playlist after the call.
Playlist playlist = new Playlist();
CommandHistory history = new CommandHistory();
history.run(new AddSongCommand(playlist, "Yesterday")); // [Yesterday]
history.run(new AddSongCommand(playlist, "Hey Jude")); // [Yesterday, Hey Jude]
history.run(new RemoveSongCommand(playlist, "Yesterday")); // [Hey Jude]
boolean undone = history.undo(); // true, [Yesterday, Hey Jude]
boolean redone = history.redo(); // true, [Hey Jude]
Notice that CommandHistory has no method such as addSong() or removeSong(). It only runs Command objects, so undo and redo work for every command we add later without changes to CommandHistory.
Next, we look at the roles in the pattern and build each class of the playlist example. After that, we add a macro command, write commands as lambdas, run them from a queue, and compare the pattern with strategy and chain of responsibility.
1. What Is the Command Pattern?
The command pattern is one of the behavioral patterns in the Gang of Four design patterns. It turns a method call into an object. The object holds the target (the receiver) and the arguments, so other code can store the call, pass it around, run it later, or reverse it.
Say a music app lets users edit a playlist from a toolbar button and from a keyboard shortcut. Without commands, both places call the Playlist methods itself. When the product team asks for an “Undo” button, both places have to record what they did, and the undo code ends up as an if-else chain over action names such as “add” and “remove”. With commands, every place creates a command object and passes it to one history object, which also handles undo.
1.1. Participants
The pattern has five roles. The table maps each role to the class that plays it in our playlist example.
| Role | What it does | Class in our example |
|---|---|---|
| Command | Declares execute() and, for undoable commands, undo() | Command interface |
| Concrete command | Stores the receiver and the arguments, and calls the receiver in execute() | AddSongCommand, RemoveSongCommand |
| Receiver | Contains the business logic that does the real work | Playlist |
| Invoker | Calls execute() on a command, and keeps commands for undo or redo | CommandHistory |
| Client | Creates the commands, connects them to the receiver and hands them to the invoker | Main (or a button handler) |
The invoker knows only the Command interface. It has no reference to Playlist, so we can add a ShuffleCommand or a RenamePlaylistCommand later without changing the invoker.

2. Command Pattern Example in Java
The example is a playlist editor with undo and redo support. It uses Java 25, records and the SequencedCollection methods from Java 21, and has no dependencies. The complete code is in the designpatterns repository on GitHub.
2.1. The Receiver
The receiver is a plain class with the business methods. It knows nothing about commands, which also means we can reuse an existing service class as a receiver without changing it.
public class Playlist {
private final List<String> songs = new ArrayList<>();
public void add(String song) {
songs.add(song);
}
public void add(int index, String song) {
songs.add(index, song);
}
public int remove(String song) {
int index = songs.indexOf(song);
if (index >= 0) {
songs.remove(index);
}
return index;
}
public void removeLast() {
if (!songs.isEmpty()) {
songs.removeLast();
}
}
public List<String> songs() {
return List.copyOf(songs);
}
}
The method remove() returns the position of the removed song, or -1 when the song is not in the list. The command needs that position later to put the song back at the same place.
2.2. The Command Interface and Concrete Commands
The Command interface declares the two operations that the invoker calls. Each concrete command keeps a reference to the receiver and the data for one request, and it translates execute() and undo() into receiver calls.
public interface Command {
void execute();
void undo();
}
Adding a song needs no extra state, so AddSongCommand is a record. Its undo() removes the last song, which is the song that execute() appended.
public record AddSongCommand(Playlist playlist, String song) implements Command {
@Override
public void execute() {
playlist.add(song);
}
@Override
public void undo() {
playlist.removeLast();
}
}
Removing a song is different, because undo must put the song back at its old position. A command that changes data must store, in execute(), everything its undo() needs to restore the previous state. So RemoveSongCommand saves the index that Playlist.remove() returns.
public class RemoveSongCommand implements Command {
private final Playlist playlist;
private final String song;
private int removedAt = -1; // state needed for undo()
public RemoveSongCommand(Playlist playlist, String song) {
this.playlist = playlist;
this.song = song;
}
@Override
public void execute() {
removedAt = playlist.remove(song);
}
@Override
public void undo() {
if (removedAt >= 0) {
playlist.add(removedAt, song);
}
}
}
The check removedAt >= 0 makes undo() safe when the song was never in the playlist. Without it, undoing new RemoveSongCommand(playlist, “Help”) on a playlist without “Help” would call add(-1, …) and throw an IndexOutOfBoundsException.
2.3. The Invoker With Undo and Redo
The invoker CommandHistory runs a command and pushes it onto an undo stack. The method undo() pops the newest command, calls its undo() and moves it to a redo stack. The method redo() does the opposite. Both stacks are ArrayDeque objects, the standard Deque implementation for stacks in Java.
public class CommandHistory {
private final Deque<Command> undoStack = new ArrayDeque<>();
private final Deque<Command> redoStack = new ArrayDeque<>();
public void run(Command command) {
command.execute();
undoStack.push(command);
redoStack.clear();
}
public boolean undo() {
Command command = undoStack.poll();
if (command == null) {
return false;
}
command.undo();
redoStack.push(command);
return true;
}
public boolean redo() {
Command command = redoStack.poll();
if (command == null) {
return false;
}
command.execute();
undoStack.push(command);
return true;
}
}
We call poll() instead of pop(), because poll() returns null on an empty stack, whereas pop() throws a NoSuchElementException. So a user who presses “Undo” more often than there are actions gets false back and no exception.
The method run() also clears the redo stack. A new action starts a new history branch, and redoing an old command on top of it would put the playlist into a state the user never saw. Text editors and IDEs behave the same way.
![Five rows showing the playlist, the undo stack and the redo stack after each call. After run(Add Yesterday) the playlist is [Yesterday] and the undo stack holds Add Yesterday. After run(Add Hey Jude) it is [Yesterday, Hey Jude]. After run(Remove Yesterday) it is [Hey Jude] with three commands on the undo stack. After undo() the playlist is [Yesterday, Hey Jude] and Remove Yesterday moves to the redo stack. After redo() the playlist is [Hey Jude] and Remove Yesterday moves back to the undo stack.](https://howtodoinjava.com/wp-content/uploads/2026/10/command-pattern-undo-redo-stacks.png)
2.4. Running the Example
The client creates the receiver and the invoker, and passes new command objects to run(). In a real app, the client code sits in the button or shortcut handlers.
Playlist playlist = new Playlist();
CommandHistory history = new CommandHistory();
history.run(new AddSongCommand(playlist, "Yesterday")); // [Yesterday]
history.run(new AddSongCommand(playlist, "Hey Jude")); // [Yesterday, Hey Jude]
history.run(new RemoveSongCommand(playlist, "Yesterday")); // [Hey Jude]
boolean undone = history.undo(); // true, [Yesterday, Hey Jude]
boolean redone = history.redo(); // true, [Hey Jude]
boolean first = history.undo(); // true, [Yesterday, Hey Jude]
boolean second = history.undo(); // true, [Yesterday]
boolean third = history.undo(); // true, []
boolean fourth = history.undo(); // false (nothing left to undo)
history.run(new AddSongCommand(playlist, "Help")); // [Help]
boolean redoAfterNew = history.redo(); // false (run() cleared the redo stack)
We can see that the undo of RemoveSongCommand put “Yesterday” back at index 0, not at the end of the list. The fourth undo() returns false because the undo stack is empty.
3. Macro Commands
A macro command is a command that holds a list of other commands and runs them as one action. It implements the same Command interface, so the invoker treats it like any single command. This is the composite pattern applied to commands.
For example, a “Party mix” button in the music app adds three songs at once. When the user presses “Undo”, all three songs should go away with one click, not one per click.
public record MacroCommand(List<Command> commands) implements Command {
public MacroCommand {
commands = List.copyOf(commands);
}
@Override
public void execute() {
commands.forEach(Command::execute);
}
@Override
public void undo() {
commands.reversed().forEach(Command::undo);
}
}
The method undo() reverts the commands in reverse order, because a later command can depend on the state that an earlier one created. The call List.reversed() comes from the sequenced collections added in Java 21 and returns a reversed view without copying the list.
Playlist party = new Playlist();
CommandHistory partyHistory = new CommandHistory();
Command partyMix = new MacroCommand(List.of(
new AddSongCommand(party, "Hey Jude"),
new AddSongCommand(party, "Help"),
new AddSongCommand(party, "Yesterday")));
partyHistory.run(partyMix); // [Hey Jude, Help, Yesterday]
boolean undone = partyHistory.undo(); // true, []
4. Writing Commands as Lambdas
A small command does not always need its own class. When a command has a single method, it is a functional interface, and a lambda can implement it. Our Command has two methods, so we add a static factory method that builds a command from two Runnable lambdas, one for the action and one for the reverse action.
public interface Command {
void execute();
void undo();
static Command of(Runnable action, Runnable reverse) {
return new Command() {
@Override
public void execute() {
action.run();
}
@Override
public void undo() {
reverse.run();
}
};
}
}
The caller passes a lambda expression and a method reference, and the history object treats the result like any other command.
Playlist playlist = new Playlist();
CommandHistory history = new CommandHistory();
Command addHelp = Command.of(() -> playlist.add("Help"), playlist::removeLast);
history.run(addHelp); // [Help]
boolean undone = history.undo(); // true, []
Lambdas fit one-line commands. A command that keeps state for undo, such as the index in RemoveSongCommand, is easier to read and test as a named class.
5. Queuing Commands and Running Them Later
A command object can wait in a list or a queue until some other code runs it, for example on a background thread. The JDK interface Runnable is a command without undo, and an ExecutorService is an invoker that runs those commands on a thread pool.
For example, the music app collects background jobs, such as syncing the playlist to the server and sending a notification email, and runs them after the user closes the editor.
List<Runnable> queue = new ArrayList<>();
queue.add(() -> System.out.println("sync playlist"));
queue.add(() -> System.out.println("send email"));
try (ExecutorService executor = Executors.newSingleThreadExecutor()) {
queue.forEach(executor::submit);
}
sync playlist
send email
Since Java 19, ExecutorService implements AutoCloseable, and its close() method waits until all submitted tasks finish. The try-with-resources block shuts the executor down even when a task throws an exception. When a queued command must return a result, we use Callable instead of Runnable, as described in Runnable vs Callable.
6. Command Pattern in the JDK and Spring
We meet the command pattern in many APIs without the word “command” in the name. In each of these APIs, our code creates the command object and the library decides when to call it.
| API | Command interface | Invoker |
|---|---|---|
| Java threads and executors | java.lang.Runnable, java.util.concurrent.Callable | Thread, ExecutorService |
| Swing | javax.swing.Action | JButton, JMenuItem |
| Spring transactions | TransactionCallback | TransactionTemplate.execute() |
| Spring JDBC | ConnectionCallback, StatementCallback | JdbcTemplate.execute() |
A Swing Action is the textbook case. One Action object goes to a toolbar button and a menu item at the same time, and disabling the action disables both.
7. When to Use the Command Pattern
The command pattern pays off when an action must exist as an object after the caller creates it, so that other code can keep it or reverse it. Undo support is the most common reason in editors, and queued background jobs are the most common reason on the server.
- The app needs undo and redo, as in editors, drawing tools or a shopping cart that can restore a removed item.
- The same action is triggered from several places, such as a button, a shortcut and a REST endpoint.
- Actions are queued, scheduled, retried or run on another thread.
- Actions are logged so that the app can replay them after a crash, for example to rebuild state from a list of saved commands.
- A user records a sequence of steps and runs it again later as a macro.
The pattern also has a cost. Every action becomes a class, so a feature with 20 actions adds 20 small classes plus the invoker. Undo is the hard part, because each command must store enough state to restore the old one, and that state takes memory when the history grows.
We keep the history bounded, e.g. the last 50 commands, and remove the oldest command when the limit is reached. When we only need to call a method and none of the cases in the list apply, a direct method call or a lambda is simpler.
8. Command Pattern FAQs
8.1. What Is the Difference Between the Command and Strategy Patterns?
A command represents one request that has already been decided, together with its receiver and arguments. A strategy represents one way of doing a task, such as a sorting or pricing algorithm, and the caller passes in the data each time. So AddSongCommand(playlist, “Help”) knows what to do and to which object, whereas a ShuffleStrategy only knows how to shuffle whatever list it gets. Both use an interface and swappable implementations, which is why the code looks alike.
8.2. Is Runnable an Example of the Command Pattern?
Yes. A Runnable wraps an action in an object with one method, run(), and the code that calls run(), for example a Thread or an ExecutorService, does not know what the action does. It is a command without undo support.
8.3. How Is the Command Pattern Different From Chain of Responsibility?
A command has one fixed receiver that handles the request. In the chain of responsibility pattern, the request goes along a chain of handlers, and each handler either processes it or passes it on to the next handler. We can combine the two, e.g. send a command object through a chain of validators before the invoker runs it.
8.4. How Does the Command Pattern Relate to the Memento Pattern?
Our commands undo their work by running the reverse action, such as removing the song they added. When a reverse action is hard to write, a command can save a snapshot of the receiver before execute() and restore the snapshot in undo(). That snapshot is a memento. Snapshots are simpler to write but use more memory for large objects.
9. Conclusion
The command pattern turns a request into an object with execute(), so the invoker that triggers the request does not depend on the receiver that does the work. In the playlist example, CommandHistory runs any Command and supports undo and redo with two ArrayDeque stacks.
Each command stores what it needs to reverse itself, as RemoveSongCommand does with the index. A macro command groups several commands into one undoable step, and small commands fit into lambdas through a factory method such as Command.of().
The JDK uses the same idea in Runnable and in Swing Action. We add command classes when we need undo, queues, logging or macros, and we stay with plain method calls when we do not.
10. References
- Runnable JavaDoc (Java 25)
- ExecutorService JavaDoc (Java 25)
- Deque JavaDoc (Java 25)
- Action JavaDoc (Java 25)
- TransactionCallback JavaDoc (Spring Framework)
Happy Learning !!
Very Clear tutorial. Thank You Author.
Hello Sir,
I read your blog regarding command pattern, which is very informative.
can you please share a link with me regarding simple way to learn spring security using different approaches like DAO etc.
Looking forward
Regards,
Salman Khan