Memento Design Pattern in Java: Complete Guide with Examples

Undo is a feature users take for granted but developers often implement badly. The naive approach — saving the entire object state as a public snapshot — violates encapsulation: once you expose internal state for storage, anything can read and modify it. The Memento pattern solves this by defining a dedicated snapshot object whose contents only the originator can read. The caretaker (your undo manager) stores and returns mementos without ever opening them.

This is not just about text editors. Any time you need rollback — database transactions, game save states, configuration checkpoints, wizard “back” buttons — you’re solving the same problem the Memento pattern addresses.

Pattern Structure

Memento design pattern structure (via refactoring.guru)
Originator creates and restores from Mementos. Caretaker stores them without reading their contents. Diagram: refactoring.guru
  • Originator — the object whose state you’re saving (Editor). Creates mementos from its current state; restores state from a memento.
  • Memento — the snapshot (EditorMemento). Immutable. Only the originator can read its contents.
  • Caretaker — manages the history (History). Holds mementos but never inspects their contents.

The Memento: Immutable and Opaque

Package-private accessors are the Java idiom for keeping memento state opaque. The getContent() and similar methods are accessible from within the memento package — the editor can call them — but external code in other packages cannot. The caretaker stores EditorMemento references but can only call what’s public (just toString()).

package memento;
public final class EditorMemento {
    private final String content;
    private final int cursorPosition;
    private final String selectedText;
    // Package-private constructor — only Editor (in same package) creates these
    EditorMemento(String content, int cursorPosition, String selectedText) {
        this.content        = content;
        this.cursorPosition = cursorPosition;
        this.selectedText   = selectedText;
    }
    // Package-private getters — only Editor can read the state back
    String getContent()      { return content; }
    int    getCursorPosition() { return cursorPosition; }
    String getSelectedText() { return selectedText; }
    @Override
    public String toString() {
        return "Snapshot[\"" + content + "\", cursor=" + cursorPosition + "]";
    }
}

The Originator (Editor)

The editor owns both save() and restore(). It packs all internal fields into a memento on save, and unpacks them from a memento on restore. No other class needs to know that the editor has a cursor position or a selection.

package memento;
public class Editor {
    private String content        = "";
    private int    cursorPosition = 0;
    private String selectedText   = "";
    public void type(String text) {
        content = content.substring(0, cursorPosition) + text + content.substring(cursorPosition);
        cursorPosition += text.length();
    }
    /** Creates a complete snapshot of current state */
    public EditorMemento save() {
        return new EditorMemento(content, cursorPosition, selectedText);
    }
    /** Restores all state from a snapshot — single atomic operation */
    public void restore(EditorMemento memento) {
        this.content        = memento.getContent();
        this.cursorPosition = memento.getCursorPosition();
        this.selectedText   = memento.getSelectedText();
    }
    @Override
    public String toString() {
        return "Editor[\"" + content + "\", cursor=" + cursorPosition + "]";
    }
}

The Caretaker (History)

The history stack holds mementos and hands them back on undo. It never calls any getter on the memento — it can’t, because the getters are package-private. It only stores and retrieves opaque objects.

package memento;
import java.util.ArrayDeque;
import java.util.Deque;
public class History {
    private final Deque<EditorMemento> snapshots = new ArrayDeque<>();
    public void save(EditorMemento memento) {
        snapshots.push(memento);
        System.out.println("  [History] Saved: " + memento);  // only sees toString()
    }
    public EditorMemento undo() {
        if (snapshots.isEmpty()) return null;
        return snapshots.pop();
    }
    public int size() { return snapshots.size(); }
}
public class Main {
    public static void main(String[] args) {
        Editor editor = new Editor();
        History history = new History();
        System.out.println("Initial: " + editor);
        editor.type("Hello");
        history.save(editor.save());
        System.out.println("After:   " + editor);
        editor.type(", World");
        history.save(editor.save());
        System.out.println("After:   " + editor);
        editor.type("! How are you?");
        System.out.println("After:   " + editor);  // NOT saved — will be lost on undo
        System.out.println("\n-- Undo --");
        editor.restore(history.undo());
        System.out.println("After undo: " + editor);
        editor.restore(history.undo());
        System.out.println("After undo: " + editor);
    }
}

Console Output

=== Memento Design Pattern Demo ===

Initial: Editor[“”, cursor=0]
  [History] Saved: Snapshot[“Hello”, cursor=5]
After:   Editor[“Hello”, cursor=5]
  [History] Saved: Snapshot[“Hello, World”, cursor=12]
After:   Editor[“Hello, World”, cursor=12]
After:   Editor[“Hello, World! How are you?”, cursor=26]

— Undo —
After undo: Editor[“Hello, World”, cursor=12]
After undo: Editor[“Hello”, cursor=5]

History empty: true

=== Demo complete ===

⚠️ Memory Cost: Each save() creates a full copy of the originator’s state. For a text editor with a 100KB document, saving a checkpoint every keystroke would consume memory fast. Real implementations use one of: bounded history (keep last N checkpoints only), delta snapshots (store only what changed), or lazy snapshots (save only on explicit user action, not automatically). Choose based on your undo granularity requirements and object size.

Serialization as Memento

Java’s Serializable mechanism is a Memento implementation. The JVM serializes an object’s state to a byte array (the memento), which can be stored anywhere — disk, network, database. Deserializing it later restores the object to the exact state it was in. This is how Java game saves, session persistence, and deep-clone via serialization work.

// Serialization as Memento
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new ObjectOutputStream(baos).writeObject(editor);  // save()
byte[] snapshot = baos.toByteArray();              // the "memento"
// Later...
Editor restored = (Editor) new ObjectInputStream(
    new ByteArrayInputStream(snapshot)).readObject();  // restore()

When to Use Memento

Reach for Memento when: you need undo/redo and the state being saved is genuinely encapsulated (clients shouldn’t see the internals). You need checkpoint/restore for a transactional operation that might need to rollback. You want to take snapshots for game save states, workflow wizard back-steps, or test fixture setup.

Avoid it when: the originator’s state is huge and snapshots are expensive — consider delta encoding or event sourcing instead. The state is already public and there’s no encapsulation to protect — a simple copy constructor or clone() is simpler than a full Memento implementation.

✅ Command + Memento Together: Command gives you fine-grained undo when each command knows its own inverse (insert → delete, add → remove). Memento gives you coarser undo when the state change is complex and hard to invert procedurally. Many real systems use both: Command for reversible operations and Memento for checkpoints before complex multi-step operations where a Command-based inverse would be too complicated to implement reliably.

Runnable Code on GitHub

Full source at ankurm.com/git.app/asmhatre/design-patterns under 03-behavioral/memento/.

javac 03-behavioral/memento/*.java -d out/memento
java -cp out/memento memento.Main

See Also

Further Reading

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.