Memento Design Pattern in Java with Real-World Example

Memento Design Pattern in Java: Complete Guide with Real-World Examples

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.

Memento Design Pattern Workflow The Originator creates a Memento containing its current state. The Caretaker stores that snapshot. Later, the Caretaker provides the saved snapshot back to the Originator, which restores its previous state, all while keeping internal data encapsulated. Originator Current object state Memento Saved snapshot Caretaker Snapshot history Create Store Retrieve snapshot and restore state Object returns to its previous state Internal state remains encapsulated
Figure: The Originator produces Mementos, the Caretaker stores them, and restoration returns a Memento to the Originator.

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
Laptop1
Mouse1
Keyboard2

The customer accidentally changes the keyboard quantity from 2 to 5. They click Undo and want to restore the previous cart.

How Memento works

Shopping Cart Undo Flow with Memento Original shopping cart with Laptop quantity one, Mouse quantity one, and Keyboard quantity two is saved as a snapshot. The customer changes the keyboard quantity to five. Undo restores the previous cart state with keyboard quantity two. Original Shopping Cart Laptop: 1 | Mouse: 1 | Keyboard: 2 Save Memento Snapshot Store complete cart state Customer Changes Quantity Keyboard: 5 Click Undo Restore Keyboard: 2 Previous shopping cart state restored
Figure: The initial cart is saved as a snapshot. Undo restores the previous keyboard quantity.

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
RecipientRahul
Amount₹5,000
DescriptionMonthly 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

  1. The application saves the initial transfer draft.
  2. The customer changes the transfer amount.
  3. The Caretaker stores the previous state.
  4. The customer clicks Undo.
  5. 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 AAAEmpty
Save BBB, AEmpty
Save CCC, B, AEmpty
UndoBB, AC
UndoAAB, C
RedoBB, AC

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:

  1. Flutter displays the shopping cart and Undo button.
  2. Spring Boot validates the authenticated user and cart ownership.
  3. The service retrieves the cart and its previous snapshot.
  4. The Originator restores the saved state.
  5. The restored cart is persisted.
  6. 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

  1. Keep Memento objects immutable. Use final fields and a private constructor so saved snapshots cannot be modified accidentally.
  2. Do not expose internal snapshot fields. The Caretaker should store and return Mementos without inspecting them.
  3. Use defensive copies for mutable objects. Copy lists, maps, and nested domain objects, or use immutable data structures.
  4. Set a maximum history size. When snapshots can accumulate, bound the number stored.
  5. Consider persistence. If state restoration must survive application restarts, snapshots need durable storage.
  6. Validate restored data. Make sure restored states remain valid under current business rules.
  7. Apply authorization before restoring sensitive data. Restoring configurations or drafts should respect ownership checks.
  8. 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:

  1. E-commerce shopping cart: Restore previous product quantities using Undo and Redo.
  2. 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.