Memento Design Pattern in Java: Complete Guide with Real-World Examples
Have you ever changed something in an application and then wished you could go back to its previous state?
For example, you accidentally changed the quantity of a product in your shopping cart, or modified the amount in a bank transfer draft and wanted to restore the previous details.
Most applications solve these problems using features such as Undo, Redo, Draft History, and Restore Previous Version. But how can we implement this functionality in Java without exposing an object's internal state?
The Memento Design Pattern in Java answers exactly that question. It lets an object hand out a sealed snapshot of its state and accept that snapshot back later, without ever exposing its private fields.
In this guide, we will understand the pattern using two practical examples — an e-commerce shopping cart and an online banking transfer draft. We will implement complete Java code, explore a Spring Boot use case, compare it with Command, and prepare for common Java design-pattern interview questions.
1. What Is the Memento Design Pattern?
The Memento pattern is a behavioral design pattern that allows an object to save its current state and restore it later without exposing its internal implementation details.
In simple words, it takes a snapshot of an object's state. Whenever we need to return to an earlier state, we can restore that snapshot.
Think about the Undo feature in a text editor:
- You write some text.
- The application saves the current state.
- You make additional changes.
- You click Undo.
- The application restores the previous state.
The Memento Pattern provides a structured way to implement this kind of functionality.
In simple words: Memento captures a snapshot of an object's state and restores it later, while keeping the object's internal data encapsulated.
Key participants
- Originator: The object whose state is saved and restored.
- Memento: An immutable snapshot of the Originator's state.
- Caretaker: Manages saved snapshots without inspecting them.
- Client: Creates the objects and triggers save, undo, and redo operations.
2. Main Components of the Memento Pattern
The pattern has three main participants.
1. Originator
The object whose state needs to be saved and restored. For example,
ShoppingCart is the Originator in our e-commerce example,
and TransferDraft is the Originator in our banking example.
It is responsible for:
- Creating a snapshot of its current state.
- Restoring its state from a previously created snapshot.
- Keeping control of its internal data.
2. Memento
An object that stores a snapshot of the Originator's state. For example, it stores the products and their quantities at a particular point in time, or the recipient, amount, and description of a transfer draft.
The snapshot should be immutable so that the saved state cannot accidentally be modified by other objects.
3. Caretaker
The object responsible for keeping and managing saved snapshots. It
does not modify the internal contents of a Memento. In our examples,
CartHistory and TransferHistory are the
Caretakers that manage Undo and Redo history.
3. Memento Pattern Workflow Diagram
This original diagram shows how the Originator creates a snapshot, how the Caretaker stores it, and how the saved state can later be restored.
How to understand this diagram:
- The Originator creates a Memento.
- The Caretaker stores the snapshot.
- When restoration is required, the Caretaker provides the saved Memento.
- The Originator restores its previous state.
4. Real-World Example 1: E-commerce Shopping Cart
Imagine an e-commerce application built using Flutter for the frontend and Spring Boot for the backend. A customer adds products to the shopping cart and changes their quantities.
Scenario
Initially, the shopping cart contains:
| Product | Quantity |
|---|---|
| Laptop | 1 |
| Mouse | 1 |
| Keyboard | 2 |
The customer accidentally changes the keyboard quantity from 2 to 5. They click Undo and want to restore the previous cart.
How Memento works
The application does not need to manually reverse every individual field modification. It can restore the complete previous cart state.
Step 1: Create the Originator and Memento – ShoppingCart
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class ShoppingCart {
private final Map<String, Integer> products =
new HashMap<>();
public void addProduct(String product, int quantity) {
products.put(product, quantity);
}
public void updateQuantity(String product, int quantity) {
if (!products.containsKey(product)) {
throw new IllegalArgumentException(
"Product not found: " + product
);
}
if (quantity <= 0) {
products.remove(product);
} else {
products.put(product, quantity);
}
}
// Create snapshot
public Memento save() {
return new Memento(products);
}
// Restore snapshot
public void restore(Memento memento) {
products.clear();
products.putAll(memento.products);
}
public void displayCart() {
System.out.println("Shopping Cart: " + products);
}
// Memento stores an independent copy
public static final class Memento {
private final Map<String, Integer> products;
private Memento(Map<String, Integer> products) {
this.products = Collections.unmodifiableMap(
new HashMap<>(products)
);
}
}
}
The Memento stores an independent copy of the product map. This is important because simply storing the original map reference would allow later modifications to affect the saved state.
Step 2: Create the Caretaker – CartHistory
import java.util.ArrayDeque;
import java.util.Deque;
public class CartHistory {
private final Deque<ShoppingCart.Memento> undoStack =
new ArrayDeque<>();
private final Deque<ShoppingCart.Memento> redoStack =
new ArrayDeque<>();
public void save(ShoppingCart cart) {
undoStack.push(cart.save());
redoStack.clear();
}
public boolean undo(ShoppingCart cart) {
if (undoStack.isEmpty()) {
return false;
}
redoStack.push(cart.save());
cart.restore(undoStack.pop());
return true;
}
public boolean redo(ShoppingCart cart) {
if (redoStack.isEmpty()) {
return false;
}
undoStack.push(cart.save());
cart.restore(redoStack.pop());
return true;
}
}
Step 3: Create the client
public class ShoppingCartDemo {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
CartHistory history = new CartHistory();
cart.addProduct("Laptop", 1);
cart.addProduct("Mouse", 1);
cart.addProduct("Keyboard", 2);
System.out.println("Initial Cart:");
cart.displayCart();
// Save the original state
history.save(cart);
// Customer accidentally changes quantity
cart.updateQuantity("Keyboard", 5);
System.out.println("After Modification:");
cart.displayCart();
// Undo
history.undo(cart);
System.out.println("After Undo:");
cart.displayCart();
// Redo
history.redo(cart);
System.out.println("After Redo:");
cart.displayCart();
}
}
Expected output
Initial Cart:
Shopping Cart: {Laptop=1, Mouse=1, Keyboard=2}
After Modification:
Shopping Cart: {Laptop=1, Mouse=1, Keyboard=5}
After Undo:
Shopping Cart: {Laptop=1, Mouse=1, Keyboard=2}
After Redo:
Shopping Cart: {Laptop=1, Mouse=1, Keyboard=5}
This shopping cart example uses Java 8-compatible code and does not require external dependencies.
How the responsibilities are separated
| Component | Responsibility |
|---|---|
ShoppingCart |
Owns the cart data and creates or restores snapshots. |
ShoppingCart.Memento |
Holds an immutable copy of the product map. |
CartHistory |
Stores snapshots in undo and redo stacks without inspecting them. |
ShoppingCartDemo |
Creates and connects the participating objects. |
5. Real-World Example 2: Online Banking Transfer Draft
Now let's take a fintech example. Imagine an online banking application where a customer prepares a money transfer. Before submitting the transfer, the customer can modify the recipient, amount, or payment description.
Scenario
The customer enters the following details:
| Field | Value |
|---|---|
| Recipient | Rahul |
| Amount | ₹5,000 |
| Description | Monthly payment |
The application saves the current draft. The customer then changes the amount to ₹8,000, realises the amount is incorrect, and clicks Undo. The application restores the previous amount of ₹5,000.
How Memento works
- The application saves the initial transfer draft.
- The customer changes the transfer amount.
- The Caretaker stores the previous state.
- The customer clicks Undo.
- The Originator restores the previous draft.
Step 1: Create the Originator and Memento – TransferDraft
import java.math.BigDecimal;
public class TransferDraft {
private String recipient;
private BigDecimal amount;
private String description;
public TransferDraft(String recipient,
BigDecimal amount,
String description) {
this.recipient = recipient;
this.amount = amount;
this.description = description;
}
public void updateTransfer(String recipient,
BigDecimal amount,
String description) {
this.recipient = recipient;
this.amount = amount;
this.description = description;
}
// Create a snapshot of the current state
public Memento save() {
return new Memento(recipient, amount, description);
}
// Restore the previous state
public void restore(Memento memento) {
this.recipient = memento.recipient;
this.amount = memento.amount;
this.description = memento.description;
}
public void display() {
System.out.println("Recipient: " + recipient);
System.out.println("Amount: " + amount);
System.out.println("Description: " + description);
System.out.println("---------------------------");
}
// Memento stores a snapshot of the Originator
public static final class Memento {
private final String recipient;
private final BigDecimal amount;
private final String description;
private Memento(String recipient,
BigDecimal amount,
String description) {
this.recipient = recipient;
this.amount = amount;
this.description = description;
}
}
}
The Memento fields are private and final. The Originator controls the creation and restoration of its snapshots. The Caretaker can store the Memento without accessing its internal fields.
Step 2: Create the Caretaker – TransferHistory
import java.util.ArrayDeque;
import java.util.Deque;
public class TransferHistory {
private final Deque<TransferDraft.Memento> undoStack =
new ArrayDeque<>();
private final Deque<TransferDraft.Memento> redoStack =
new ArrayDeque<>();
public void save(TransferDraft draft) {
undoStack.push(draft.save());
// A new change invalidates old Redo history
redoStack.clear();
}
public boolean undo(TransferDraft draft) {
if (undoStack.isEmpty()) {
return false;
}
// Save current state before moving backwards
redoStack.push(draft.save());
// Restore previous snapshot
draft.restore(undoStack.pop());
return true;
}
public boolean redo(TransferDraft draft) {
if (redoStack.isEmpty()) {
return false;
}
// Save current state before moving forwards
undoStack.push(draft.save());
// Restore next snapshot
draft.restore(redoStack.pop());
return true;
}
}
Step 3: Create the client
import java.math.BigDecimal;
public class TransferDemo {
public static void main(String[] args) {
TransferDraft draft = new TransferDraft(
"Rahul",
new BigDecimal("5000.00"),
"Monthly payment"
);
TransferHistory history = new TransferHistory();
System.out.println("Initial Transfer:");
draft.display();
// Save the initial state
history.save(draft);
// Change transfer details
draft.updateTransfer(
"Rahul",
new BigDecimal("8000.00"),
"Updated monthly payment"
);
System.out.println("After Modification:");
draft.display();
// Undo the changes
if (history.undo(draft)) {
System.out.println("After Undo:");
draft.display();
}
// Redo the changes
if (history.redo(draft)) {
System.out.println("After Redo:");
draft.display();
}
}
}
Expected output
Initial Transfer:
Recipient: Rahul
Amount: 5000.00
Description: Monthly payment
---------------------------
After Modification:
Recipient: Rahul
Amount: 8000.00
Description: Updated monthly payment
---------------------------
After Undo:
Recipient: Rahul
Amount: 5000.00
Description: Monthly payment
---------------------------
After Redo:
Recipient: Rahul
Amount: 8000.00
Description: Updated monthly payment
---------------------------
Important: Memento is not payment reversal
This banking example applies only to an unfinished transfer draft. Once a payment request has been sent to an acquirer, Memento must not be used to reverse or undo the financial transaction.
Actual payment failure handling, uncertain acquirer responses, refunds, reversals, idempotency, and reconciliation require dedicated payment workflows.
6. How Undo and Redo Work Internally
Both examples use two stacks:
undoStack— stores previous states.redoStack— stores states that can be restored again.
Suppose a customer changes the keyboard quantity as follows:
- State A: Quantity = 2
- State B: Quantity = 3
- State C: Quantity = 5
When the customer clicks Undo, the current state is moved to the Redo stack, and the previous state is restored. When the customer clicks Redo, the current state is saved to the Undo stack, and the next state is restored.
| Action | Current state | Undo stack (top first) | Redo stack (top first) |
|---|---|---|---|
| Save A | A | A | Empty |
| Save B | B | B, A | Empty |
| Save C | C | C, B, A | Empty |
| Undo | B | B, A | C |
| Undo | A | A | B, C |
| Redo | B | B, A | C |
Whenever a new change is made after Undo, the old Redo history should normally be cleared. This is the expected behaviour of most Undo/Redo systems.
7. Memento Design Pattern in Spring Boot
You might be wondering whether we can use the Memento Pattern in a Spring Boot application.
Yes. It can be useful for:
- Shopping cart history.
- Saving and restoring user form drafts.
- Maintaining configuration change history.
- Implementing Undo/Redo in an administrative dashboard.
- Restoring previous versions of editable business objects.
Flutter + Spring Boot e-commerce flow
For a Flutter + Spring Boot e-commerce application, the flow could look like this:
- Flutter displays the shopping cart and Undo button.
- Spring Boot validates the authenticated user and cart ownership.
- The service retrieves the cart and its previous snapshot.
- The Originator restores the saved state.
- The restored cart is persisted.
- Spring Boot returns the updated cart to Flutter.
Is this always the best design?
No. For a real application, do not keep user history only in an in-memory Java collection if it must survive application restarts or work across multiple backend instances. Use persistent storage, appropriate concurrency controls, ownership checks, and retention policies.
Also, restoring a shopping cart does not guarantee the same product price, promotion, or inventory availability. Revalidate these details before checkout.
8. Advantages and Disadvantages of the Memento Pattern
Advantages
- Encapsulation: The internal state of the Originator is not exposed to the Caretaker.
- Easy restoration: Objects can return to a previously saved state.
- Supports Undo and Redo: It provides a clean foundation for history-based features.
- Separation of responsibilities: The Originator creates and restores snapshots, while the Caretaker manages them.
- Cleaner code: State restoration logic does not need to be scattered across the application.
Disadvantages
- Memory consumption: Saving too many snapshots can consume significant memory.
- Large objects: Objects with many fields or nested collections can make snapshots expensive.
- Deep copying: Mutable objects may need defensive copying to prevent saved states from changing unexpectedly.
- History management: Applications must decide how many snapshots to keep and when to remove them.
- Distributed applications: In-memory snapshots are not automatically available across multiple application instances.
The best memento has a bounded scope. If snapshots begin to capture entire object graphs or sensitive data, consider capturing only the fields required for restoration and applying a history limit.
9. Best Practices When Using Memento
- Keep Memento objects immutable. Use final fields and a private constructor so saved snapshots cannot be modified accidentally.
- Do not expose internal snapshot fields. The Caretaker should store and return Mementos without inspecting them.
- Use defensive copies for mutable objects. Copy lists, maps, and nested domain objects, or use immutable data structures.
- Set a maximum history size. When snapshots can accumulate, bound the number stored.
- Consider persistence. If state restoration must survive application restarts, snapshots need durable storage.
- Validate restored data. Make sure restored states remain valid under current business rules.
- Apply authorization before restoring sensitive data. Restoring configurations or drafts should respect ownership checks.
- Separate Memento from database transactions. Never treat Memento as a replacement for database transactions, payment reversal, or financial reconciliation.
10. Memento vs Command Pattern
Both Memento and Command can be used to implement Undo functionality, but they solve the problem differently.
| Feature | Memento Pattern | Command Pattern |
|---|---|---|
| Main purpose | Save and restore object state | Encapsulate an operation as an object |
| Undo approach | Restore a previous snapshot | Execute a reverse operation or retain previous information |
| State management | Stores object state | Stores the operation and its required data |
| Typical example | Restore a previous shopping cart | Undo a product quantity update operation |
Memento remembers what the object looked like, while Command remembers an operation that can be executed. They can also work together, with a command storing a memento to support undo.
11. Java Interview Questions and Answers
Q1. What is the Memento Design Pattern in Java?
Memento is a behavioral design pattern that captures an object's internal state and allows it to restore that state later without exposing its implementation details.
Q2. Is Memento a creational, structural, or behavioral pattern?
It is a behavioral design pattern because it deals with how objects manage and restore their state.
Q3. What are the three main participants in Memento?
Originator, Memento, and Caretaker. The Originator creates and restores state, the Memento stores the snapshot, and the Caretaker manages the snapshots.
Q4. What is the responsibility of the Originator?
The Originator creates a Memento containing its current state and restores its state from a previously created Memento.
Q5. What is the responsibility of the Caretaker?
The Caretaker stores and manages Memento objects. It should not modify the internal state contained in those objects.
Q6. Does Memento expose the internal state of an object?
No. A properly designed Memento keeps the saved state encapsulated. The Originator controls how snapshots are created and restored.
Q7. What is the difference between a shallow copy and a deep copy in Memento?
A shallow copy copies references to nested mutable objects, so changes to those objects may affect the saved snapshot. A deep copy also copies the nested mutable data, keeping the snapshot independent.
Q8. Can Memento be used to implement Undo and Redo?
Yes. The Caretaker can use two stacks to manage previous and future snapshots. One stack supports Undo and the other supports Redo.
Q9. Why should a Memento be immutable?
Immutability prevents saved snapshots from being modified accidentally. It ensures that the restored state remains consistent with the state originally captured.
Q10. What happens when a user makes a new change after clicking Undo?
The Redo history should normally be cleared because the user has created a new branch of changes. The previous forward history is no longer applicable.
Q11. What are the disadvantages of the Memento Pattern?
It can consume considerable memory when many snapshots are stored. Deep copying large objects and managing snapshot history can also add complexity.
Q12. Can we use Memento with Spring Boot?
Yes. It can be used for draft restoration, shopping cart history, configuration history, and Undo/Redo features. For distributed applications, snapshots may need to be stored in a shared persistent system.
Q13. What is the difference between Memento and Command?
Memento saves an object's state so it can be restored later. Command encapsulates an operation as an object, which can be executed, queued, logged, or sometimes reversed.
Q14. Is Memento suitable for storing database transaction history?
Not by itself. Memento is designed for capturing and restoring application object state. Financial transaction history, audit records, and transaction reversals require dedicated persistence and business controls.
Q15. What Java collections can be used to implement Undo and Redo?
A Deque implemented with ArrayDeque is a good
choice for stack-based Undo and Redo history. It provides efficient
push and pop operations.
12. Frequently Asked Questions
Can we implement Memento without using a separate class?
Yes. You can use a nested class, a record (Java 16+), or another suitable representation. The important requirement is that the saved state remains encapsulated and cannot be modified unexpectedly.
Does Memento automatically save an object to a database?
No. Memento only defines the state-capturing and restoration approach. Persistence must be implemented separately if snapshots need to survive application restarts.
Is Memento useful in real-world applications?
Yes. It is useful in editors, shopping carts, form builders, configuration tools, draft management, and applications that need to restore previous object states.
Is the Memento pattern part of the Gang of Four patterns?
Yes. Memento is one of the behavioral patterns in the Gang of Four design-pattern catalog.
Can there be more than one Caretaker in an application?
Yes. Separate Caretakers can manage history for different originators or workflows, which helps keep each history bounded and easy to reason about.
Does Memento mean all changes must be reversible?
No. The pattern restores a previously captured state. It does not guarantee that a business action can be reversed. Actions that have external side effects require separate compensating business processes.
13. Quick Revision Checklist
- Memento is a behavioral design pattern.
- It captures and restores an object's state without exposing internals.
- The Originator creates and consumes snapshots.
- The Memento is an immutable snapshot of the Originator's state.
- The Caretaker stores snapshots but never inspects them.
- Undo and Redo are commonly implemented with two stacks.
- A new change after Undo clears the Redo stack.
- Shopping cart and banking transfer drafts are classic examples.
- Memento does not replace database transactions or payment reversal.
- Bounded history and defensive copying help control memory and correctness.
Conclusion
The Memento Design Pattern is a useful way to manage object state without breaking encapsulation. It gives us a clean approach to implementing features such as Undo, Redo, Draft History, and Restore Previous Version.
In this guide, we explored two practical examples:
- E-commerce shopping cart: Restore previous product quantities using Undo and Redo.
- Online banking transfer draft: Restore previous transfer details before submission.
The most important thing to remember is this: Memento stores the previous state, while the Originator is responsible for restoring it.
If you're learning Java design patterns, try extending the shopping cart example with multiple Undo operations, a maximum history size, and database-backed snapshots. That will help you understand how the pattern works beyond a simple demonstration.
If you are preparing for Java interviews, practise implementing Memento alongside Command, Observer, and State. Understanding when each pattern is useful matters more than memorizing its definition.