Mediator Design Pattern in Java: Complete Guide with Examples

Five components in a UI form — a checkbox, a dropdown, two text fields, and a submit button — all need to react when each other changes. The checkbox enables the dropdown; the dropdown choice changes which text fields are required; the submit button enables only when all required fields are filled. If each component talks directly to the others, you end up with 5 × 4 = 20 direct dependencies. Add a sixth component and you need to update potentially five existing ones. The Mediator pattern replaces all those direct links with a single hub: components talk only to the mediator, and the mediator coordinates everyone.

The pattern is best understood as the opposite of tight coupling. Without it, N components need O(N²) connections. With it, N components each need exactly 1 connection to the mediator — O(N) total. The tradeoff is that the mediator itself becomes a concentrator of coordination logic, so it needs to be kept focused and testable.

Pattern Structure

Mediator design pattern structure (via refactoring.guru)
Colleagues communicate only through the Mediator. Each colleague knows the mediator but not the other colleagues. Diagram: refactoring.guru

Implementation: Chat Room

A chat room is the canonical Mediator example because it makes the connection topology obvious: users don’t connect to each other — they connect to the room.

package mediator;
/** Mediator interface — the hub that all colleagues communicate through */
public interface ChatMediator {
    void sendMessage(String message, User sender);
    void addUser(User user);
}
package mediator;
/**
 * Colleague — knows only the mediator, not other users.
 * When Alice wants to message everyone, she tells the room.
 * The room decides who receives it. Alice doesn't know who's there.
 */
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 from) {
        System.out.println("  [" + name + "] received from " + from + ": " + message);
    }
}
package mediator;
import java.util.ArrayList;
import java.util.List;
/**
 * Concrete Mediator — routes messages between users.
 * All coordination logic lives here. Users are completely decoupled from each other.
 */
public class ChatRoom implements ChatMediator {
    private final List<User> users = new ArrayList<>();
    @Override
    public void addUser(User user) {
        users.add(user);
        System.out.println("  [ChatRoom] " + user.getName() + " joined");
    }
    @Override
    public void sendMessage(String message, User sender) {
        for (User user : users) {
            if (user != sender) {
                user.receive(message, sender.getName());
            }
        }
    }
}
public class Main {
    public static void main(String[] args) {
        ChatRoom room = new ChatRoom();
        User alice = new User("Alice", room);
        User bob   = new User("Bob",   room);
        User carol = new User("Carol", room);
        room.addUser(alice); room.addUser(bob); room.addUser(carol);
        System.out.println();
        alice.send("Hey everyone!");
        System.out.println();
        bob.send("Hi Alice and Carol!");
    }
}

Console Output

=== Mediator Design Pattern Demo ===

  [ChatRoom] Alice joined
  [ChatRoom] Bob joined
  [ChatRoom] Carol joined

[Alice] sends: Hey everyone!
  [Bob] received from Alice: Hey everyone!
  [Carol] received from Alice: Hey everyone!

[Bob] sends: Hi Alice and Carol!
  [Alice] received from Bob: Hi Alice and Carol!
  [Carol] received from Bob: Hi Alice and Carol!

— Connections without Mediator: 3 users need 6 direct links —
— With Mediator: 3 users each connect only to the room —
— With N users: O(N) connections instead of O(N²) —

=== Demo complete ===

Mediator in Practice: MVC and Spring Events

MVC (Model-View-Controller) is an architectural application of the Mediator pattern. The Controller is the mediator: the View and Model don’t talk to each other directly — they talk through the Controller. The View fires actions to the Controller; the Controller updates the Model; the Controller updates the View. Removing the direct View-Model coupling is what makes MVC testable and what lets you swap views without touching the model.

Spring’s ApplicationEventPublisher is another implementation. Components publish events to the Spring context (the mediator). Other components subscribe to event types. Publishers and subscribers are completely decoupled — a payment service publishes OrderPaidEvent; the email service, the inventory service, and the analytics service each subscribe to it independently. Adding a new subscriber doesn’t require changing the publisher.

// Spring as Mediator — publisher and subscriber have zero direct dependency
@Service
public class PaymentService {
    private final ApplicationEventPublisher events;
    public void processPayment(Order order) {
        // ... payment logic ...
        events.publishEvent(new OrderPaidEvent(order));  // tell the mediator
    }
}
@Component
public class EmailService {
    @EventListener
    public void onOrderPaid(OrderPaidEvent event) {  // mediator delivers here
        // send confirmation email
    }
}

💡 Mediator vs Observer: They look similar but have different intents. Observer establishes a one-to-many notification channel from a single subject to its subscribers — the subject knows subscribers exist (it manages the list). Mediator establishes many-to-many communication through a hub — colleagues don’t know who else is connected. In Observer, colleagues can still know each other through the subject. In Mediator, colleagues are fully anonymous to each other. Use Observer for one-directional event broadcast; use Mediator when multiple peers need coordinated two-way interaction.

When to Use Mediator

Reach for Mediator when: a set of objects communicate in complex, tightly coupled ways and the coupling is causing change ripple — modifying one object requires modifying several others. You want to reuse individual components in different contexts without dragging their web of cross-dependencies with them. Multiple components need coordinated behaviour and you want that coordination logic in one testable place.

Avoid it when: the mediator becomes a God Object — if it grows to know too much about too many things, you’ve just moved the coupling rather than reduced it. For simple cases, direct references are clearer and easier to trace. If components communicate in one direction (A notifies B), Observer is simpler.

✅ Keep the Mediator Focused: The mediator should coordinate one cohesive group of related objects. A chat room mediates users in a room. A form mediator coordinates inputs on one form. If a single mediator is coordinating components across entirely unrelated domains, split it. A mediator that’s grown to orchestrate your entire application’s objects is an anti-pattern (the “God Mediator”). Each mediator should cover one bounded coordination concern.

Runnable Code on GitHub

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

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

See Also

Further Reading

Leave a Reply

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