Showing posts with label Behavioral Design Patterns. Show all posts
Showing posts with label Behavioral Design Patterns. Show all posts

Template Method Design Pattern in Java with Real-World Examples

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

Have you ever noticed that different payment methods follow almost the same process?

For example, whether a customer makes a payment using UPI, a debit card, or a wallet, the application generally performs these steps:

  1. Validate the payment request.
  2. Check payment conditions.
  3. Execute the payment.
  4. Save the transaction details.
  5. Send a notification.

The overall payment process remains almost the same, but the actual payment execution changes depending on the payment method.

Now imagine writing this complete workflow separately for every payment method. You would probably end up with duplicated code.

The Template Method Design Pattern in Java solves exactly this problem. It defines the overall algorithm in a parent class and allows subclasses to customize individual steps.

In this guide, we will understand the pattern using a tea-and-coffee example and a real-world payment processing scenario, implement complete executable Java code, explore a Spring Boot use case, compare it with Strategy, and prepare for common Java design-pattern interview questions.

1. What Is the Template Method Design Pattern?

The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in a superclass. Some steps are implemented in the parent class, while other steps are left to subclasses.

The important point is that the parent class controls the execution order.

In simple words: The parent class decides what happens and in which order. The child class decides how certain steps happen.

Real-world example: Preparing tea and coffee

Think about preparing tea and coffee. Both follow a common process:

  1. Boil water.
  2. Add the main ingredient.
  3. Add milk or other ingredients.
  4. Serve the drink.

However, the ingredients are different. For tea, we add tea leaves. For coffee, we add coffee powder. The overall preparation process remains the same, but certain steps change.

This is the basic idea behind the Template Method Pattern.

Key participants

  • Abstract Class: Defines the template method and common steps.
  • Template Method: Defines the fixed sequence of operations.
  • Concrete Class: Implements the steps that vary.
  • Hook Method: An optional method with a default implementation that subclasses can override.

2. Important Components of the Pattern

The pattern has four key building blocks.

1. Abstract Class

Defines the template method and common steps. It may declare abstract methods for steps that subclasses must provide and concrete methods for steps that are shared.

2. Template Method

Defines the fixed sequence of operations. It usually calls common methods, abstract methods, and optional hook methods in a specific order. Making it final prevents subclasses from changing the sequence.

3. Concrete Class

Implements the steps that vary between different variants of the algorithm. For example, UPIPayment, CardPayment, and WalletPayment provide their own executePayment() implementations.

4. Hook Method

An optional method that subclasses can override to customize a step. It has a default implementation in the parent class, so subclasses that do not need customization can simply ignore it.

3. Template Method Class Diagram

This original diagram shows how an abstract payment class defines the common workflow, while UPI, card, and wallet payment classes provide their own payment implementation.

Template Method Class Diagram with UPI, Card, and Wallet Subclasses AbstractPayment defines the final processPayment() template method, a private validatePayment() step, an abstract executePayment() method, and an overridable beforePayment() hook. UPIPayment, CardPayment, and WalletPayment extend AbstractPayment and provide their own executePayment() implementations. WalletPayment also overrides the beforePayment() hook. AbstractPayment + processPayment() : void [final] - validatePayment() : void # executePayment() : void [abstract] # beforePayment() : void [hook] UPIPayment executePayment() UPI-specific logic CardPayment executePayment() Card-specific logic WalletPayment executePayment() Wallet-specific logic Three subclasses · One common template All subclasses inherit processPayment()
Figure: The abstract parent class defines the workflow, and concrete subclasses provide their own step implementations.

Important: The abstract class contains the common workflow. Each child class provides its own implementation of executePayment(). The wallet class can additionally override the optional hook method.

4. How the Template Method Works Internally

Let's understand the execution sequence.

  1. The client creates an object of a concrete payment class.
  2. The client calls processPayment().
  3. The parent class validates the payment request.
  4. The parent class calls beforePayment().
  5. The child class implementation of executePayment() is invoked.
  6. The parent class saves the transaction and sends a notification.

The important point is that the client never assembles the workflow itself. The parent class owns the sequence, and the subclass only supplies the pieces that differ.

What changes after introducing a template method?

The common steps live in one place. Each concrete subclass contains only the implementation of the specific steps that vary. Adding a new payment method becomes a matter of adding a new subclass rather than duplicating the whole workflow.

5. Execution Flow Diagram

This diagram represents the common sequence followed by UPI, card, and wallet payments. Only the payment execution implementation changes.

Template Method Payment Execution Flow The client calls processPayment(), which validates the request, runs the beforePayment() hook, executes the subclass-specific payment step, saves the transaction, and sends a notification. The parent class controls the execution order. Client calls processPayment() 1. Validate payment request 2. Run beforePayment() hook 3. Execute payment (subclass) UPIPayment UPI execution CardPayment Card execution WalletPayment Wallet execution 4. Save transaction 5. Send notification
Figure: The template method runs the workflow in a fixed order, delegating the payment execution step to the subclass.

The parent class controls the sequence before and after payment execution. The child class only supplies the payment-specific behavior.

6. Java Implementation – Tea and Coffee Example

Before implementing a payment system, let's create a simple example using tea and coffee. This example makes the pattern easy to see before applying it to a real workflow.

Complete executable Java code

Create a file named BeverageDemo.java and copy the following code.

abstract class Beverage {

    // Template Method
    public final void prepareBeverage() {
        boilWater();
        addMainIngredient();
        addCondiments();
        serve();
    }

    private void boilWater() {
        System.out.println("Boiling water...");
    }

    protected abstract void addMainIngredient();

    protected abstract void addCondiments();

    private void serve() {
        System.out.println("Serving beverage...");
    }
}

class Tea extends Beverage {

    @Override
    protected void addMainIngredient() {
        System.out.println("Adding tea leaves...");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding milk and sugar...");
    }
}

class Coffee extends Beverage {

    @Override
    protected void addMainIngredient() {
        System.out.println("Adding coffee powder...");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding milk and sugar...");
    }
}

public class BeverageDemo {

    public static void main(String[] args) {

        Beverage tea = new Tea();

        System.out.println("Preparing Tea:");
        tea.prepareBeverage();

        System.out.println();

        Beverage coffee = new Coffee();

        System.out.println("Preparing Coffee:");
        coffee.prepareBeverage();
    }
}

Expected output

Preparing Tea:
Boiling water...
Adding tea leaves...
Adding milk and sugar...
Serving beverage...

Preparing Coffee:
Boiling water...
Adding coffee powder...
Adding milk and sugar...
Serving beverage...

Understand this carefully: Both classes execute the same prepareBeverage() method. However, the implementation of addMainIngredient() and addCondiments() comes from the respective child class.

Also notice the final keyword. It prevents subclasses from overriding the template method and changing the overall sequence.

7. Real-World Example – Payment Processing System

Now let's understand a practical example from a fintech application.

Suppose you are developing a payment gateway that supports three payment methods:

  • UPI payment
  • Debit or credit card payment
  • Wallet payment

All three methods need common processing steps, but each payment method has its own execution logic. Without a proper design, developers may write the same validation, transaction-saving, and notification code in multiple classes.

Let's solve this problem using the Template Method Pattern.

Complete executable Java 8 code

For simplicity, we will simulate the payment providers using console messages. In a real application, the payment execution methods would communicate with external payment APIs or internal payment services.

Create a file named PaymentDemo.java.

abstract class AbstractPayment {

    private final double amount;

    public AbstractPayment(double amount) {
        this.amount = amount;
    }

    // Template Method
    public final void processPayment() {

        validatePayment();

        beforePayment();

        executePayment();

        saveTransaction();

        sendNotification();

        System.out.println("Payment completed successfully.");
    }

    private void validatePayment() {

        if (amount <= 0) {
            throw new IllegalArgumentException(
                "Payment amount must be greater than zero"
            );
        }

        System.out.println("Payment validation successful.");
    }

    // Optional hook method
    protected void beforePayment() {
        System.out.println("Checking payment conditions...");
    }

    // Subclasses implement their own payment logic
    protected abstract void executePayment();

    private void saveTransaction() {
        System.out.println("Saving transaction: Rs. " + amount);
    }

    private void sendNotification() {
        System.out.println("Sending payment notification...");
    }

    protected double getAmount() {
        return amount;
    }
}

class UPIPayment extends AbstractPayment {

    public UPIPayment(double amount) {
        super(amount);
    }

    @Override
    protected void executePayment() {
        System.out.println(
            "Processing UPI payment of Rs. " + getAmount()
        );
    }
}

class CardPayment extends AbstractPayment {

    public CardPayment(double amount) {
        super(amount);
    }

    @Override
    protected void executePayment() {
        System.out.println(
            "Processing Card payment of Rs. " + getAmount()
        );
    }
}

class WalletPayment extends AbstractPayment {

    public WalletPayment(double amount) {
        super(amount);
    }

    @Override
    protected void beforePayment() {
        System.out.println("Checking wallet balance...");
    }

    @Override
    protected void executePayment() {
        System.out.println(
            "Processing Wallet payment of Rs. " + getAmount()
        );
    }
}

public class PaymentDemo {

    public static void main(String[] args) {

        System.out.println("----- UPI PAYMENT -----");

        AbstractPayment upiPayment =
            new UPIPayment(1000);

        upiPayment.processPayment();

        System.out.println();

        System.out.println("----- CARD PAYMENT -----");

        AbstractPayment cardPayment =
            new CardPayment(2000);

        cardPayment.processPayment();

        System.out.println();

        System.out.println("----- WALLET PAYMENT -----");

        AbstractPayment walletPayment =
            new WalletPayment(500);

        walletPayment.processPayment();
    }
}

Let's understand the important code

1. Why is processPayment() final?

The processPayment() method is the template method. It defines the payment workflow. Making it final prevents subclasses from overriding the method and changing the required execution sequence.

2. Why is executePayment() abstract?

Because each payment method has a different implementation. UPI, card, and wallet payments may communicate with different services or providers.

3. Why is beforePayment() a hook method?

Not every payment method needs the same additional operation. The parent class provides a default implementation, and a child class can override it when necessary. In our example, the wallet payment checks its balance before executing the payment.

4. Why are saveTransaction() and sendNotification() common methods?

Because the basic transaction-saving and notification workflow is shared by all payment types in this simplified example. Therefore, we avoid duplicating these operations in every child class.

8. Payment Workflow Diagram

This original diagram shows the shared payment template and the three concrete payment implementations that plug into it.

Payment Template Method Workflow A common payment template defines the processPayment() sequence of validate, execute, save, and notify. UPI, card, and wallet payments plug into the execute step with their own implementations, while sharing the transaction and notification workflow. Common Payment Template processPayment() Validate → Execute → Save → Notify Same sequence for every payment type UPI Payment UPI provider API Custom execution Card Payment Card provider API Custom execution Wallet Payment Wallet service Custom execution Different implementations, same overall algorithm Common transaction and notification workflow
Figure: The shared payment template defines the workflow; each payment type only supplies its execution step.

9. Expected Output

----- UPI PAYMENT -----
Payment validation successful.
Checking payment conditions...
Processing UPI payment of Rs. 1000.0
Saving transaction: Rs. 1000.0
Sending payment notification...
Payment completed successfully.

----- CARD PAYMENT -----
Payment validation successful.
Checking payment conditions...
Processing Card payment of Rs. 2000.0
Saving transaction: Rs. 2000.0
Sending payment notification...
Payment completed successfully.

----- WALLET PAYMENT -----
Payment validation successful.
Checking wallet balance...
Processing Wallet payment of Rs. 500.0
Saving transaction: Rs. 500.0
Sending payment notification...
Payment completed successfully.

Notice that all three payments execute the same processPayment() template, but the executePayment() step changes for each payment method. The wallet payment also overrides the beforePayment() hook to check the balance.

How the responsibilities are separated

Component Responsibility
AbstractPayment Defines the workflow and common steps.
processPayment() Fixed template method controlling the execution sequence.
executePayment() Abstract step implemented by each concrete payment class.
beforePayment() Optional hook overridable by subclasses when needed.
UPIPayment / CardPayment / WalletPayment Provide their own execution logic.

10. Template Method in Spring Boot

The Template Method Pattern can be useful in Spring Boot applications when multiple services follow a common sequence of operations but have different implementations for one or more steps.

Some examples include:

  • Payment processing: Common validation, transaction handling, and notification workflow.
  • File processing: Read file, validate records, transform data, and save results.
  • Report generation: Load data, process records, generate a report, and export it.
  • Authentication: Common authentication flow with different authentication mechanisms.
  • Third-party integration: Common request preparation, provider-specific execution, and response handling.

Example: Spring Boot file processing

Suppose your Spring Boot application processes CSV and Excel files. Both file types follow a similar workflow:

  1. Read the file.
  2. Validate its contents.
  3. Transform the data.
  4. Save the records.

You can create an abstract processor that defines this workflow and separate CSV and Excel processors that implement the file-specific steps. This keeps common processing logic in one place.

Is this always the best design?

No. A class hierarchy is not always the right answer. If the workflow itself needs to change frequently, or if you need to select an algorithm at runtime, the Strategy pattern may be a better fit.

Production consideration: In a real Spring Boot application, also consider dependency injection, transaction boundaries, exception handling, retry policies, idempotency, and external API failure handling. A template method alone does not provide these capabilities.

11. Template Method vs Strategy Pattern

Both patterns help reduce duplicated code, but they solve different problems.

Comparison Template Method Strategy
Type Behavioral Behavioral
Approach Inheritance Composition
Algorithm Defines a fixed workflow Encapsulates interchangeable algorithms
Customization Subclasses override steps Different strategy implementations
Execution order Controlled by parent class Controlled by the calling object
Flexibility Suitable for fixed workflows Flexible for runtime selection

Template Method: Every payment follows the same sequence, but the payment execution step changes.

Strategy: The application selects a payment strategy, such as UPI, card, or wallet, and delegates payment execution to that strategy.

Template Method uses inheritance to vary individual steps of an algorithm. Strategy uses composition to replace an entire algorithm.

12. Advantages and Disadvantages of the Template Method Pattern

Advantages

  • Code reusability: Common logic is written once in the parent class.
  • Less code duplication: Subclasses implement only the steps that differ.
  • Consistent workflow: The parent class controls the sequence of operations.
  • Easy maintenance: Changes to common operations can be made in one place.
  • Controlled customization: Subclasses can customize only the permitted steps.

Disadvantages

  • Inheritance dependency: It relies on a parent-child relationship, which creates tight coupling.
  • Limited flexibility: Changing the workflow may require modifying the parent class.
  • More subclasses: Multiple variations can increase the number of classes.
  • Debugging complexity: Execution moves between parent and child methods, which can make debugging harder.
  • Subclass coupling: Child classes depend on the structure and protected methods of the parent class.

The best template method is small, focused, and stable. If the workflow begins to change frequently or needs to be selected at runtime, consider moving to a composition-based design instead.

13. Best Practices

  1. Keep the template method small and easy to understand. The sequence of steps should be readable at a glance.
  2. Use the final keyword when subclasses must not change the algorithm sequence.
  3. Keep common operations in the parent class. Avoid duplicating the same logic in each subclass.
  4. Use abstract methods only for steps that must be implemented by subclasses.
  5. Use hook methods for optional customization. Provide sensible defaults so subclasses can override only when necessary.
  6. Avoid adding too many customization points without a real requirement. More hooks increase complexity without adding value.
  7. Do not use inheritance just to share a few utility methods. Composition is usually a better fit for that case.
  8. Write unit tests for the common workflow and each concrete implementation. This ensures the sequence remains intact as the design evolves.

14. Java Interview Questions and Answers

Q1. What is the Template Method Design Pattern?

Template Method is a behavioral design pattern that defines the skeleton of an algorithm in a superclass and allows subclasses to implement or customize specific steps without changing the overall algorithm structure.

Q2. Why is Template Method called a behavioral design pattern?

Because it focuses on how an algorithm is executed and how responsibilities are distributed between the parent and child classes.

Q3. What is a template method?

A template method is the method that defines the sequence of steps of an algorithm. It usually calls common methods, abstract methods, and optional hook methods.

Q4. Why is the template method often declared final?

To prevent subclasses from overriding the workflow and changing the predefined order of execution. However, declaring it final is a design choice, not an absolute requirement of the pattern.

Q5. What is a hook method?

A hook method is an optional method with a default implementation in the parent class. Subclasses can override it to customize behavior when required.

Q6. What is the difference between an abstract method and a hook method?

An abstract method has no implementation in the abstract class and must be implemented by a concrete subclass. A hook method has a default implementation and can optionally be overridden.

Q7. Can we implement Template Method without an abstract class?

Yes. A concrete parent class can define the template method and allow subclasses to override protected methods. However, an abstract class is commonly used when some algorithm steps must be implemented by subclasses.

Q8. What is the difference between Template Method and Strategy?

Template Method uses inheritance and defines a fixed algorithm structure. Strategy uses composition and allows the application to select an interchangeable algorithm implementation.

Q9. Can a template method call private methods?

Yes. The template method can call private methods for operations that must remain internal to the parent class. Subclasses cannot override those private methods.

Q10. Can a subclass change the execution order?

If the template method is final, the subclass cannot override the workflow. It can only customize the steps that the parent class exposes for customization.

Q11. What is the Hollywood Principle in relation to Template Method?

The Hollywood Principle means "Don't call us, we'll call you." The parent class controls the algorithm and calls the subclass methods when necessary. Subclasses do not control the overall workflow.

Q12. Is Template Method suitable for microservices?

It can be useful inside a microservice when multiple workflows share common steps. For example, different file processors or payment handlers may share a common processing sequence. It does not replace communication or orchestration between microservices.

Q13. Can Template Method be used with Spring Boot?

Yes. An abstract Spring-managed service can define a common workflow, while concrete Spring components implement the specific steps. Dependency injection can be used for supporting services.

Q14. What happens if a subclass does not implement an abstract method?

The subclass must either implement the abstract method or remain abstract. Otherwise, Java compilation fails.

Q15. When should we avoid Template Method?

Avoid it when the steps of the algorithm change frequently, when you need to select algorithms dynamically at runtime, or when inheritance creates unnecessary coupling. Strategy or composition may be a better choice in such cases.

15. Frequently Asked Questions

Is Template Method part of Java?

No. It is a design pattern, not a built-in Java API. You can implement it using Java classes, inheritance, and method overriding.

Is Template Method the same as a Java template class?

No. Java does not have C++-style templates. Template Method is a design pattern implemented through inheritance and polymorphism.

Can we use interfaces for Template Method?

An interface with default methods can provide some similar behavior, but an abstract class is generally more suitable when you need shared state, constructors, protected methods, and abstract steps.

Where is Template Method useful in real projects?

It is useful in payment processing, file processing, report generation, authentication workflows, and third-party integrations where the overall sequence is common but individual steps differ.

Is the Template Method pattern part of the Gang of Four patterns?

Yes. Template Method is one of the behavioral patterns in the Gang of Four design-pattern catalog.

Does Template Method mean all steps must be fixed?

No. The sequence is fixed, but individual steps can be implemented, overridden, or optionally skipped through hooks depending on the design.

16. Quick Revision Checklist

  • Template Method is a behavioral design pattern.
  • It defines the skeleton of an algorithm in a superclass.
  • The template method controls the execution order.
  • Subclasses customize specific steps without changing the sequence.
  • Making the template method final protects the workflow.
  • Hook methods provide optional customization with sensible defaults.
  • Payment processing, file processing, and report generation are typical uses.
  • Template Method uses inheritance; Strategy uses composition.
  • Spring Boot services can use it for common workflows across concrete components.
  • Avoid it when the workflow changes frequently or must be selected at runtime.

Conclusion

The Template Method Design Pattern is useful when multiple classes follow the same algorithm but need different implementations for certain steps. Instead of duplicating the entire workflow, we define the common sequence in a parent class and let subclasses customize only the required operations.

Remember these three important points:

  • The parent class defines the algorithm structure.
  • Child classes customize specific steps.
  • The template method controls the execution order.

Before using this pattern in a real project, check whether your workflow is genuinely common. If the entire algorithm needs to change dynamically, consider the Strategy Pattern instead.

If you are preparing for Java interviews, practise implementing Template Method alongside Strategy, Observer, and Factory Method. Understanding when each pattern is useful matters more than memorizing its definition.

Memento Design Pattern in Java with Real-World Example

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.

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

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

Have you ever worked on a Java application where one class depends on several other classes, and those classes also depend on one another? At first, everything may look simple. As new features arrive, however, changing one class can require changes across the entire application.

The Mediator Design Pattern in Java helps solve this problem by introducing a central object that coordinates communication between related objects. Instead of knowing about one another directly, the participating objects communicate through the mediator.

In this guide, we will understand the pattern using a chat room example, implement it using Java interfaces and classes, explore a Spring Boot use case, compare it with Observer and Facade, and prepare for common Java design-pattern interview questions.

1. What Is the Mediator Design Pattern?

The Mediator pattern is a behavioral design pattern that defines an object responsible for coordinating how a group of objects interact with one another.

Without a mediator, each object may need references to several other objects to perform its work. A mediator reduces these direct dependencies by providing a common communication point.

For example, in a chat application, a user should not need to maintain references to every other user in the room. The user sends a message to the chat room, and the chat room determines how to distribute it.

In simple words: Mediator centralizes communication so that objects do not need to coordinate all their interactions themselves.

Key participants

  • Mediator: Defines the communication operations.
  • Concrete Mediator: Implements the coordination rules.
  • Colleague: A participating object that communicates through the mediator.
  • Client: Creates or wires the objects and the mediator together.

2. A Real-World Analogy: Airport Control Tower

Imagine several aircraft approaching an airport. If every aircraft independently coordinated all takeoffs and landings with every other aircraft, managing their interactions would become difficult.

An airport control tower coordinates clearances and provides relevant instructions. Aircraft communicate with the tower rather than relying on direct coordination between every aircraft.

In software, a mediator serves a similar coordination role. The participating objects perform their own responsibilities while the mediator manages the interactions between them.

This is an analogy for the communication structure, not a claim that real-world air traffic control is implemented exactly like a software design pattern.

Where can this idea be applied?

  • A chat room that distributes messages among connected users.
  • A form controller that coordinates multiple user-interface fields.
  • A checkout workflow that coordinates inventory, payment, and order creation.
  • A workflow coordinator that manages interactions among application services.

3. The Problem Without the Mediator Pattern

Consider an online shopping application with four components: OrderService, PaymentService, InventoryService, and NotificationService.

When a customer places an order, the order component might need to coordinate payment, inventory, and notification operations. If those components also start calling one another directly, the dependency structure can become difficult to understand.

OrderService
   |--------> PaymentService
   |--------> InventoryService
   |--------> NotificationService

PaymentService
   |--------> NotificationService

InventoryService
   |--------> NotificationService

This is not automatically bad design. Direct dependencies can be perfectly reasonable in a small, clear workflow. The problem appears when the number of cross-component interactions grows and the same coordination logic gets scattered across multiple classes.

Common symptoms

  • Many classes hold references to several other classes.
  • Changing a workflow requires editing multiple components.
  • Coordination rules are duplicated in different places.
  • Unit tests need many dependencies just to test one interaction.
  • It is difficult to identify where the overall workflow is controlled.

The Mediator pattern can help when the real problem is complicated communication among related objects. It should not be added merely because a system has several classes.

4. How the Mediator Pattern Works

The general flow is straightforward:

  1. The client creates a mediator and the participating objects.
  2. Each participating object receives a reference to the mediator.
  3. An object reports an action or sends a request through the mediator.
  4. The mediator decides which participating objects should receive the request.
  5. The relevant objects perform their own responsibilities.

The important point is that a colleague does not need to know every other colleague's implementation details. It knows the mediator contract and uses that contract to request coordination.

What changes after introducing a mediator?

The direct connections between participating objects are reduced. Their interaction logic becomes easier to locate because the mediator provides a central place for the rules governing their communication.

This does not mean all business logic should move into the mediator. A well-designed mediator coordinates interactions and delegates the actual work to the appropriate components.

5. Mediator Design Pattern Diagram

This original diagram illustrates the difference between direct communication and mediator-based communication.

Mediator Design Pattern Architecture Three user objects communicate through a central chat mediator. The mediator routes messages to the intended recipients, so users do not need direct references to one another. User A Colleague User B Colleague ChatMediator Coordinates messages send deliver User C Colleague Key idea Colleagues communicate through the mediator, not through direct references to every other colleague.
Figure: A central mediator coordinates communication among participating objects.

6. Complete Java Implementation: Chat Room Example

A chat room is a useful way to understand the pattern because users need to send messages to one another without managing all the connections themselves.

We will implement four classes or interfaces:

  • ChatMediator — the mediator interface.
  • ChatRoom — the concrete mediator.
  • User — the colleague abstraction.
  • ChatRoomDemo — the client that wires everything together.

Step 1: Create the ChatMediator interface

public interface ChatMediator {

    void addUser(User user);

    void sendMessage(String message, User sender);
}

The interface defines two operations: registering a user and coordinating message delivery. User objects depend on this contract rather than on the concrete chat room implementation.

Step 2: Create the User class

public class User {

    private final String name;
    private final ChatMediator mediator;

    public User(String name, ChatMediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }

    public String getName() {
        return name;
    }

    public void send(String message) {
        System.out.println(name + " sends: " + message);
        mediator.sendMessage(message, this);
    }

    public void receive(String message, String senderName) {
        System.out.println(
            name + " received from " + senderName + ": " + message
        );
    }
}

Notice that a user has a reference to the mediator, not a collection of all the other users. When the user sends a message, it delegates message distribution to the mediator.

Step 3: Implement the concrete ChatRoom mediator

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class ChatRoom implements ChatMediator {

    private final List<User> users = new ArrayList<>();

    @Override
    public void addUser(User user) {
        users.add(Objects.requireNonNull(user));
    }

    @Override
    public void sendMessage(String message, User sender) {
        Objects.requireNonNull(message);
        Objects.requireNonNull(sender);

        for (User user : users) {
            if (user != sender) {
                user.receive(message, sender.getName());
            }
        }
    }
}

The chat room maintains the registered users and distributes a message to everyone except the sender. The rule for message distribution lives in one place instead of being repeated in every user object.

This simple implementation is intended for a single-threaded demo. A production chat system would also need decisions about thread safety, user identity, message validation, delivery guarantees, disconnected users, and access control.

Step 4: Create the client

public class ChatRoomDemo {

    public static void main(String[] args) {

        ChatMediator chatRoom = new ChatRoom();

        User aman = new User("Aman", chatRoom);
        User priya = new User("Priya", chatRoom);
        User rahul = new User("Rahul", chatRoom);

        chatRoom.addUser(aman);
        chatRoom.addUser(priya);
        chatRoom.addUser(rahul);

        aman.send("Hello everyone!");
        System.out.println();

        priya.send("Hi Aman!");
    }
}

The client creates the mediator, creates the users with that mediator, registers them, and then sends messages. The users do not need references to one another.

7. Expected Output

Aman sends: Hello everyone!
Priya received from Aman: Hello everyone!
Rahul received from Aman: Hello everyone!

Priya sends: Hi Aman!
Aman received from Priya: Hi Aman!
Rahul received from Priya: Hi Aman!

The output demonstrates the mediator's role. Aman sends a message through the chat room, and the chat room distributes it to Priya and Rahul. The sender does not receive their own message in this implementation.

How the responsibilities are separated

Component Responsibility
ChatMediator Defines the communication contract.
ChatRoom Registers users and routes messages.
User Sends messages and handles received messages.
ChatRoomDemo Creates and connects the participating objects.

This separation makes the example easier to extend. For example, the mediator could later route messages to a specific user or group, provided those behaviors are added to the communication contract and implemented consistently.

8. Mediator Design Pattern in Spring Boot

In Spring Boot, the same coordination idea can be useful when several components participate in one workflow. Consider an order-placement process that needs to coordinate inventory reservation, payment authorization, and notification.

The following example shows an application-level coordinator. It is deliberately small so that the responsibility of the mediator remains clear.

Define the collaborating services

public interface InventoryService {
    void reserve(String orderId);
}
public interface PaymentService {
    void authorize(String orderId);
}
public interface NotificationService {
    void sendOrderConfirmation(String orderId);
}

Create the order workflow mediator

import org.springframework.stereotype.Component;

@Component
public class OrderWorkflowMediator {

    private final InventoryService inventoryService;
    private final PaymentService paymentService;
    private final NotificationService notificationService;

    public OrderWorkflowMediator(
            InventoryService inventoryService,
            PaymentService paymentService,
            NotificationService notificationService) {
        this.inventoryService = inventoryService;
        this.paymentService = paymentService;
        this.notificationService = notificationService;
    }

    public void placeOrder(String orderId) {

        inventoryService.reserve(orderId);

        paymentService.authorize(orderId);

        notificationService.sendOrderConfirmation(orderId);
    }
}

Call the mediator from an application service

import org.springframework.stereotype.Service;

@Service
public class OrderApplicationService {

    private final OrderWorkflowMediator workflowMediator;

    public OrderApplicationService(
            OrderWorkflowMediator workflowMediator) {
        this.workflowMediator = workflowMediator;
    }

    public void placeOrder(String orderId) {
        workflowMediator.placeOrder(orderId);
    }
}

The application service delegates the workflow to one coordinator. The coordinator knows which operations to invoke and in what order. Each underlying service remains responsible for its own operation.

Is this always the best design?

No. A class that simply calls three services in sequence may be a reasonable application service or workflow orchestrator; whether calling it a mediator is useful depends on the broader interaction design. Do not add a pattern name just to make the architecture look more sophisticated.

In a production order workflow, inventory reservation and payment authorization also require explicit failure handling, idempotency, transaction boundaries, and compensation or recovery logic. A mediator does not automatically provide these guarantees.

9. Mediator Pattern in Microservices

The mediator idea can be applied to application components, but it should not be confused with a message broker or an entire microservices architecture.

Imagine an order workflow involving several independent services:

  1. The order workflow begins after a customer places an order.
  2. Inventory is reserved.
  3. Payment is authorized.
  4. The order status is updated.
  5. A notification is sent.

A coordinator may manage the sequence of operations. In a distributed system, the implementation could use synchronous service calls, asynchronous messaging, or a durable workflow engine depending on the reliability and scaling requirements.

Application mediator vs message broker

Application mediator Message broker
Coordinates interactions defined in application code. Transports and routes messages between producers and consumers.
May invoke collaborating services directly. May provide queues, topics, acknowledgements, and delivery-related capabilities.
Does not inherently provide durable messaging. Durability and delivery behavior depend on the broker and its configuration.

In a Spring Boot application, a mediator-like coordinator might call application services. Kafka, RabbitMQ, or another messaging system can be used for asynchronous communication when the architecture needs it. These technologies solve related but different problems.

10. Advantages and Disadvantages of the Mediator Pattern

Advantages

  • Reduced direct coupling: Colleagues do not need to maintain references to every other participating object.
  • Centralized coordination: Interaction rules are easier to find and review.
  • Easier maintenance: Changes to interaction rules can often be made in the mediator instead of multiple colleagues.
  • Improved testability: Collaborators can be tested against a mediator interface or with mocked dependencies.
  • Flexible interactions: The mediator can determine which objects participate in a particular interaction.

Disadvantages

  • God object risk: A mediator can grow into a large class that knows too much about every part of the application.
  • More indirection: Understanding a simple interaction may require navigating through an additional class.
  • Complex coordination logic: If the mediator contains many branching rules, it can become difficult to test and maintain.
  • Potential runtime overhead: Additional delegation can introduce some overhead, though design clarity is usually the more important consideration.

The best mediator has a clear scope. If it begins handling unrelated workflows, business rules, persistence, retries, and notification formatting, consider splitting those responsibilities into separate components.

11. Mediator vs Observer vs Facade

These patterns can appear similar because all can reduce dependencies or simplify communication. Their intentions, however, are different.

Mediator vs Observer

Mediator Observer
Coordinates interactions among a group of objects. Notifies interested observers when a subject changes or emits an event.
The mediator decides how a request should be coordinated. The subject publishes a notification and observers react.
Useful for workflow and interaction coordination. Useful for event notification and subscriptions.

For example, a chat room that decides how to distribute a message illustrates Mediator. A component that notifies multiple listeners when its state changes illustrates Observer.

Mediator vs Facade

Mediator Facade
Coordinates communication among participating objects. Provides a simplified interface to a complex subsystem.
Colleagues typically use the mediator to coordinate interactions. Clients use the facade to access subsystem functionality more easily.
Focuses on collaboration and interaction rules. Focuses on simplifying access to a subsystem.

Mediator vs Strategy

Mediator Strategy
Coordinates communication among objects. Encapsulates interchangeable algorithms.
Useful when object interactions become complicated. Useful when a behavior can be implemented in different ways.
Example: routing messages among chat users. Example: selecting a different payment fee calculation algorithm.

12. Mediator Pattern and SOLID Principles

The Mediator pattern can support cleaner object-oriented design, especially when direct dependencies between colleagues are becoming difficult to manage. It does not automatically make an application SOLID, but it can help when applied carefully.

Single Responsibility Principle (SRP)

A mediator can own the coordination rules while each colleague remains responsible for its own behavior. Avoid moving every business rule into the mediator, or it may become responsible for too much.

Open/Closed Principle (OCP)

Depending on an interface such as ChatMediator allows a colleague to work with different mediator implementations without depending directly on a concrete class. New behavior may still require changes to the mediator contract or implementation.

Dependency Inversion Principle (DIP)

High-level components can depend on a mediator abstraction instead of a concrete mediator. This makes dependencies easier to replace in tests and alternate configurations.

Practical takeaway

Use the mediator to reduce unnecessary knowledge between collaborating objects, while keeping each component's own responsibilities clear. A central coordinator should not become a place where every unrelated responsibility is collected.

13. Best Practices for Experienced Java Developers

  1. Keep the mediator focused. Give it a clear coordination responsibility rather than making it the owner of all business logic.
  2. Use interfaces when they add value. A mediator interface is helpful when multiple implementations, test doubles, or clear abstraction boundaries are needed.
  3. Make dependencies explicit. Constructor injection is a good default for required collaborators in Spring Boot applications.
  4. Define failure behavior. Decide what should happen if one collaborator fails or returns an unsuccessful result.
  5. Test the coordination rules. Verify which collaborators are called, in what order, and how the mediator responds to failures.
  6. Do not confuse coordination with orchestration infrastructure. A simple Java mediator does not provide durable workflows, distributed transactions, retries, or message delivery guarantees automatically.
  7. Choose asynchronous messaging deliberately. Use events or a message broker when asynchronous decoupling, independent processing, or durable messaging is actually required.
  8. Watch for the God object problem. If one mediator grows too large, split it by workflow or responsibility.
  9. Do not introduce unnecessary abstraction. If two objects have a simple, stable relationship, direct communication may be clearer.

14. Java Interview Questions and Answers

Q1. What is the Mediator Design Pattern?

It is a behavioral design pattern that encapsulates how a group of objects interact, reducing the need for direct references between those objects.

Q2. Why is Mediator called a behavioral pattern?

It focuses on how objects communicate and collaborate rather than on how objects are created or how their structure is composed.

Q3. What problem does the Mediator pattern solve?

It helps manage complicated interactions between related objects by moving coordination rules into a dedicated mediator.

Q4. What are the main participants in the Mediator pattern?

The main participants are the mediator interface, concrete mediator, colleagues that communicate through it, and the client that wires the objects together.

Q5. Give a real-world example of Mediator in Java.

A chat room is a common example. Users send messages through the chat mediator, which decides which other users should receive them.

Q6. How does Mediator reduce coupling?

Participating objects depend on the mediator instead of holding direct references to every other participating object. This reduces the number of direct dependencies between colleagues.

Q7. What is the biggest disadvantage of the Mediator pattern?

The mediator can become a God object if it accumulates too many responsibilities or complex business rules.

Q8. What is the difference between Mediator and Observer?

Mediator coordinates interactions among objects. Observer focuses on notifying subscribed observers when a subject changes or emits an event. Both can be used together in some designs.

Q9. What is the difference between Mediator and Facade?

Mediator manages interactions between colleagues, whereas Facade simplifies how clients access a subsystem.

Q10. Can Mediator be used with Spring Boot?

Yes. A Spring-managed coordinator can organize interactions among services. Whether it should be called a mediator, application service, or workflow orchestrator depends on its responsibilities and the design context.

Q11. Is Spring ApplicationEventPublisher the Mediator pattern?

Not exactly. Spring application events provide a mechanism for publishing events and notifying listeners. That is closer to an event-driven Observer-style relationship. Event handling may participate in a broader coordination design, but the mechanisms should not be treated as interchangeable.

Q12. Can a mediator be asynchronous?

Yes. A mediator can coordinate asynchronous operations, but the implementation must define how completion, errors, ordering, timeouts, and cancellation are handled.

Q13. Does Mediator eliminate all coupling?

No. It reduces direct coupling between colleagues but introduces dependencies on the mediator contract. The mediator itself also depends on the objects it coordinates.

Q14. When should you avoid the Mediator pattern?

Avoid it when interactions are already simple and easy to understand, or when the mediator would merely add another layer without reducing meaningful complexity.

Q15. How would you test a mediator?

Unit tests can verify that the mediator sends the right requests to collaborators, respects the expected sequence, and handles failures correctly. Collaborators can often be replaced with mocks or fakes.

15. Frequently Asked Questions

Is Mediator a creational, structural, or behavioral pattern?

Mediator is a behavioral design pattern because it deals with communication and coordination between objects.

Is the Mediator pattern part of the Gang of Four patterns?

Yes. Mediator is one of the behavioral patterns in the Gang of Four design-pattern catalog.

Can there be more than one mediator in an application?

Yes. An application can have separate mediators for different groups of objects or workflows. Keeping their responsibilities separate can prevent a single mediator from becoming too large.

Does Mediator mean all communication must be synchronous?

No. The pattern describes how interactions are coordinated, not whether the operations must complete synchronously. An implementation can coordinate synchronous calls or asynchronous operations.

Can a mediator use dependency injection?

Yes. In Spring Boot, constructor injection is commonly used to supply the collaborators that a mediator or workflow coordinator needs.

Is Mediator the same as an API Gateway?

No. An API Gateway handles concerns such as routing and cross-cutting API behavior at an application boundary. A mediator coordinates interactions among participating objects. A gateway may coordinate some operations, but the two concepts have different purposes.

16. Quick Revision Checklist

  • Mediator is a behavioral design pattern.
  • It encapsulates interaction rules between related objects.
  • Colleagues communicate through a mediator instead of knowing every other colleague.
  • A concrete mediator implements the coordination logic.
  • A chat room is a simple, practical example.
  • Mediator reduces direct coupling but does not eliminate all dependencies.
  • Observer focuses on notifications; Mediator focuses on coordination.
  • Facade simplifies access to a subsystem.
  • A mediator should not become a God object.
  • In Spring Boot, distinguish a mediator from a general application service or distributed workflow engine.

Conclusion

The Mediator Design Pattern is useful when multiple objects need to collaborate and their direct dependencies are becoming difficult to maintain. By moving interaction rules into a dedicated coordinator, the design can become easier to understand, test, and change.

The chat room example demonstrates the core idea: users send messages through a mediator instead of maintaining direct connections to every other user. The same principle can help coordinate related components in a Java or Spring Boot application.

The key is to apply the pattern where it solves a real design problem. Keep the mediator focused, let each collaborator own its own behavior, and avoid introducing unnecessary layers into simple workflows.

If you are preparing for Java interviews, practise implementing Mediator alongside Observer, Strategy, Chain of Responsibility, and Command. Understanding when each pattern is useful matters more than memorizing its definition.