Skip to content
3 min read

🏛️ Mediator Design Pattern

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and lets you vary their interaction independently.


🌍 Real-World Analogy

Analogy — Air Traffic Control Tower

At a busy airport, planes don't communicate directly with each other to coordinate landing and takeoff — that would be chaos. Instead, the control tower (Mediator) manages all communication. Pilots (Colleagues) talk only to the tower, and the tower coordinates their interactions. Adding a new runway or aircraft type doesn't require changing how planes communicate with each other.

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    P1(["✈️ Flight AA101"]) -->|"talks to"| T{{"🗼 Control Tower"}}
    P2(["✈️ Flight BA202"]) -->|"talks to"| T
    T -->|"coordinates"| R1(["🛬 Runway 1"])
    T -->|"coordinates"| R2(["🛫 Runway 2"])

    style T fill:#FFF3E0,stroke:#E65100,stroke-width:2px,color:#000
    style P1 fill:#E3F2FD,stroke:#1565C0,color:#000
    style P2 fill:#E3F2FD,stroke:#1565C0,color:#000
    style R1 fill:#E8F5E9,stroke:#2E7D32,color:#000
    style R2 fill:#E8F5E9,stroke:#2E7D32,color:#000

🏗️ Pattern Structure

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    Mediator[["🏛️ Mediator"]]
    ConcreteMediator{{"📡 ConcreteMediator"}}
    Colleague[["👥 Colleague"]]
    ColleagueA{{"🅰️ ColleagueA"}}
    ColleagueB{{"🅱️ ColleagueB"}}

    ConcreteMediator -->|"implements"| Mediator
    ColleagueA -->|"extends"| Colleague
    ColleagueB -->|"extends"| Colleague
    Colleague -.->|"talks to"| Mediator
    ConcreteMediator -.->|"coordinates"| ColleagueA
    ConcreteMediator -.->|"coordinates"| ColleagueB

    style Mediator fill:#FFF3E0,stroke:#E65100,color:#000
    style ConcreteMediator fill:#FFF3E0,stroke:#E65100,color:#000
    style Colleague fill:#E3F2FD,stroke:#1565C0,color:#000
    style ColleagueA fill:#E8F5E9,stroke:#2E7D32,color:#000
    style ColleagueB fill:#E8F5E9,stroke:#2E7D32,color:#000

UML Class Diagram

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
classDiagram
    class ChatMediator {
        <<interface>>
        +sendMessage(String message, User sender) void
        +sendPrivateMessage(String message, User sender, User receiver) void
        +addUser(User user) void
        +removeUser(User user) void
    }
    class ChatRoom {
        -roomName: String
        -users: List~User~
        +sendMessage(String message, User sender) void
        +sendPrivateMessage(String message, User sender, User receiver) void
        +addUser(User user) void
        +removeUser(User user) void
    }
    class User {
        <<abstract>>
        #mediator: ChatMediator
        #name: String
        +send(String message)* void
        +receive(String message)* void
        +getName() String
    }
    class PremiumUser {
        +send(String message) void
        +receive(String message) void
        +sendPrivate(String message, User receiver) void
    }
    class RegularUser {
        +send(String message) void
        +receive(String message) void
    }

    ChatRoom ..|> ChatMediator
    User --> ChatMediator : communicates via
    PremiumUser --|> User
    RegularUser --|> User
    ChatRoom --> User : coordinates

❓ The Problem

When many objects need to communicate with each other:

  • N objects communicating directly leads to N*(N-1)/2 connections — a tangled mesh
  • Adding a new object requires modifying all existing objects it interacts with
  • Objects become tightly coupled — you can't reuse one without bringing its dependencies
  • The interaction logic is scattered across many classes

Example: A chat room with 50 users. Without a mediator, each user would need references to 49 other users. Adding user #51 would require modifying all 50 existing users.

Without This Pattern

Java
public class User {
    private String name;
    private List<User> contacts = new ArrayList<>();

    public void addContact(User other) {
        contacts.add(other);
        other.contacts.add(this); // bidirectional coupling
    }

    public void sendMessage(String message) {
        // Must know about ALL other users directly
        for (User contact : contacts) {
            contact.receive(name + ": " + message);
        }
    }

    public void receive(String message) {
        System.out.println(name + " got: " + message);
    }
}
// Adding user #51 means calling addContact() on all 50 existing users.
// Want to add "message filtering"? Modify EVERY User class.
  • N-squared coupling — each object holds direct references to every other object it communicates with (mesh topology)
  • Adding a new participant modifies existing code — every existing user must register the new one, violating Open/Closed Principle
  • Scattered interaction logic — rules like "mute user X" or "broadcast only to premium users" must be duplicated in every participant
  • Cannot reuse independently — extracting a single User class is impossible because it depends on concrete references to other users
  • Pain point: A product manager asks for "message read receipts." Without a mediator, you must add read-receipt logic to every single User subclass, multiplying the complexity across 50+ classes

✅ The Solution

The Mediator pattern introduces a central coordinator:

  1. Mediator interface — defines the communication protocol
  2. Concrete Mediator — implements coordination logic, knows all colleagues
  3. Colleague (abstract) — holds a reference to the mediator, communicates through it
  4. Concrete Colleagues — interact with each other only through the mediator

Objects go from a mesh topology to a star topology — all communication flows through the center.


💻 Implementation

Java
// Mediator interface
public interface ChatMediator {
    void sendMessage(String message, User sender);
    void sendPrivateMessage(String message, User sender, User receiver);
    void addUser(User user);
    void removeUser(User user);
}

// Concrete Mediator
public class ChatRoom implements ChatMediator {
    private final String roomName;
    private final List<User> users = new ArrayList<>();

    public ChatRoom(String roomName) {
        this.roomName = roomName;
    }

    @Override
    public void addUser(User user) {
        users.add(user);
        System.out.println("📢 " + user.getName() + " joined " + roomName);
        // Notify others
        users.stream()
            .filter(u -> !u.equals(user))
            .forEach(u -> u.receive("🔔 " + user.getName() + " has joined the chat"));
    }

    @Override
    public void removeUser(User user) {
        users.remove(user);
        System.out.println("📢 " + user.getName() + " left " + roomName);
    }

    @Override
    public void sendMessage(String message, User sender) {
        String formatted = sender.getName() + ": " + message;
        users.stream()
            .filter(u -> !u.equals(sender))
            .forEach(u -> u.receive(formatted));
    }

    @Override
    public void sendPrivateMessage(String message, User sender, User receiver) {
        if (users.contains(receiver)) {
            receiver.receive("[DM from " + sender.getName() + "]: " + message);
        }
    }
}

// Colleague
public abstract class User {
    protected final ChatMediator mediator;
    protected final String name;

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

    public abstract void send(String message);
    public abstract void receive(String message);
    public String getName() { return name; }
}

// Concrete Colleagues
public class PremiumUser extends User {
    public PremiumUser(ChatMediator mediator, String name) {
        super(mediator, name);
    }

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

    @Override
    public void receive(String message) {
        System.out.println("  ⭐ " + name + " received: " + message);
    }

    public void sendPrivate(String message, User receiver) {
        mediator.sendPrivateMessage(message, this, receiver);
    }
}

public class RegularUser extends User {
    public RegularUser(ChatMediator mediator, String name) {
        super(mediator, name);
    }

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

    @Override
    public void receive(String message) {
        System.out.println("  👤 " + name + " received: " + message);
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        ChatMediator chatRoom = new ChatRoom("Java Developers");

        User alice = new PremiumUser(chatRoom, "Alice");
        User bob = new RegularUser(chatRoom, "Bob");
        User charlie = new RegularUser(chatRoom, "Charlie");

        chatRoom.addUser(alice);
        chatRoom.addUser(bob);
        chatRoom.addUser(charlie);

        alice.send("Hey everyone!");
        bob.send("Hi Alice!");
        ((PremiumUser) alice).sendPrivate("Secret message", bob);
    }
}
Java
// Mediator for form components
public interface FormMediator {
    void notify(Component sender, String event);
}

// Concrete Mediator — coordinates form interactions
public class RegistrationFormMediator implements FormMediator {
    private TextField emailField;
    private TextField passwordField;
    private CheckBox termsCheckbox;
    private Button submitButton;

    public void setComponents(TextField email, TextField password,
                              CheckBox terms, Button submit) {
        this.emailField = email;
        this.passwordField = password;
        this.termsCheckbox = terms;
        this.submitButton = submit;
    }

    @Override
    public void notify(Component sender, String event) {
        if (sender == emailField && event.equals("textChanged")) {
            validateEmail();
        } else if (sender == passwordField && event.equals("textChanged")) {
            validatePassword();
        } else if (sender == termsCheckbox && event.equals("toggled")) {
            updateSubmitButton();
        } else if (sender == submitButton && event.equals("clicked")) {
            submitForm();
        }
        updateSubmitButton();
    }

    private void validateEmail() {
        boolean valid = emailField.getText().matches(".*@.*\\..*");
        emailField.setValid(valid);
    }

    private void validatePassword() {
        boolean valid = passwordField.getText().length() >= 8;
        passwordField.setValid(valid);
    }

    private void updateSubmitButton() {
        boolean canSubmit = emailField.isValid() &&
                           passwordField.isValid() &&
                           termsCheckbox.isChecked();
        submitButton.setEnabled(canSubmit);
    }

    private void submitForm() {
        System.out.println("✅ Form submitted!");
    }
}

// Base Component
public abstract class Component {
    protected FormMediator mediator;

    public void setMediator(FormMediator mediator) {
        this.mediator = mediator;
    }

    protected void changed(String event) {
        mediator.notify(this, event);
    }
}

🎯 When to Use

  • When a set of objects communicate in complex but well-defined ways
  • When reusing an object is difficult because it communicates with many other objects
  • When you want to customize interaction behavior without subclassing many components
  • When you need to reduce chaotic dependencies between tightly-coupled classes
  • When object interactions should be centralized for maintainability

🏭 Real-World Examples

Framework/Library Usage
java.util.Timer Mediates between timer tasks and thread scheduling
Spring ApplicationContext Mediates between beans — they don't reference each other directly
Java Message Service (JMS) Message broker mediates between producers and consumers
java.util.concurrent.Executor Mediates between task submission and task execution
Spring MVC DispatcherServlet Mediates between request and controllers/views
JavaFX/Swing Event System Event bus mediates between UI components
Apache Kafka Broker Mediates between producers and consumer groups

⚠️ Pitfalls

Common Mistakes

  • God Object — The mediator can grow into a monolithic class doing too much. Split by concern if needed.
  • Single point of failure — All communication flows through one object; if it fails, everything stops.
  • Hidden complexity — Moving logic into the mediator makes individual colleagues simpler but the mediator complex. Don't just relocate the mess.
  • Performance bottleneck — In high-throughput systems, the mediator can become a bottleneck if it's synchronous.
  • Over-use — If only 2-3 objects interact, direct communication is simpler. Mediator shines with 5+ interconnected objects.

📝 Key Takeaways

Summary

  • Mediator replaces many-to-many relationships with many-to-one (star topology)
  • Colleagues only know the mediator — they are fully decoupled from each other
  • Follows Single Responsibility — interaction logic is centralized in one place
  • Mediator vs. Observer: Mediator is bidirectional (coordinates), Observer is unidirectional (notifies)
  • Mediator vs. Facade: Facade simplifies an interface; Mediator coordinates behavior between peers
  • In enterprise systems, message brokers (Kafka, RabbitMQ) are distributed mediators