Observer Pattern in Java – Publish-Subscribe Mechanism

Illustration for Observer Pattern in Java – Publish-Subscribe Mechanism
By Last updated:

Introduction

The Observer Pattern is a behavioral design pattern that defines a one-to-many dependency between objects. When one object (subject) changes its state, all its dependents (observers) are automatically notified and updated. This is also known as the Publish-Subscribe mechanism.

It’s used extensively in GUI applications, event-driven systems, real-time notifications, and more. From handling button clicks in Swing to implementing custom event listeners in backend systems, Observer Pattern helps decouple components and promote extensibility.


☑️ Core Intent and Participants

Intent: Establish a one-to-many relationship so that when the subject changes state, all its observers are notified.

Participants:

  • Subject (Publisher): Maintains a list of observers and notifies them of changes.
  • Observer (Subscriber): Defines an updating interface and gets notified by the subject.

UML Diagram (Textual)

+------------------+       +------------------+
|     Subject      |<>---->|     Observer     |
+------------------+       +------------------+
| +attach()        |       | +update()        |
| +detach()        |       +------------------+
| +notifyObservers()|
+------------------+

🚀 Real-World Use Cases

  • GUI frameworks like Java Swing (e.g., ActionListeners)
  • Messaging systems like Kafka and RabbitMQ
  • Stock price monitoring apps
  • Notification systems in social media apps
  • News feed updates in real-time apps
  • File change listeners (e.g., WatchService API)

💡 Java Implementation Strategy

Step 1: Define the Observer Interface

public interface Observer {
    void update(String message);
}

Step 2: Create the Subject Class

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

public class Subject {
    private List<Observer> observers = new ArrayList<>();
    private String state;

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void detach(Observer observer) {
        observers.remove(observer);
    }

    public void setState(String state) {
        this.state = state;
        notifyObservers();
    }

    private void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(state);
        }
    }
}

Step 3: Concrete Observers

public class EmailSubscriber implements Observer {
    private String name;

    public EmailSubscriber(String name) {
        this.name = name;
    }

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

public class SMSSubscriber implements Observer {
    private String name;

    public SMSSubscriber(String name) {
        this.name = name;
    }

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

Step 4: Demo

public class Demo {
    public static void main(String[] args) {
        Subject newsletter = new Subject();

        Observer emailSub = new EmailSubscriber("Alice");
        Observer smsSub = new SMSSubscriber("Bob");

        newsletter.attach(emailSub);
        newsletter.attach(smsSub);

        newsletter.setState("New edition of Java Weekly is out!");
    }
}

✅ Pros and Cons

✅ Pros

  • Decouples subject and observers
  • Flexible and extensible
  • Supports dynamic subscription
  • Promotes single responsibility

❌ Cons

  • Can lead to memory leaks if observers are not properly removed
  • Complex debugging in large systems
  • Notification order is not guaranteed

🔥 Anti-patterns and Misuse

  • Holding strong references in subject → causes memory leaks
  • Performing heavy operations inside update()
  • Not handling exceptions inside observers
  • Overusing for trivial updates (adds unnecessary indirection)

Pattern Purpose
Observer One-to-many notifications
Mediator Centralized communication between objects
Event Bus (Guava) Global publish-subscribe communication
Strategy Replaces switch statements with polymorphism

🔄 Refactoring Legacy Code

If your legacy code has tight coupling between event producers and consumers, consider introducing Observer interfaces and allowing dynamic registration of listeners.


🧠 Best Practices

  • Use WeakReference for observers in memory-sensitive apps
  • Prefer java.util.Observer / Observable (deprecated now) only for legacy code
  • Consider EventBus or RxJava in large-scale systems
  • Always log or handle exceptions in update()

🧩 Real-World Analogy

YouTube Channels and Subscribers
When a YouTuber (Subject) uploads a new video (state change), all subscribers (Observers) get a notification. The YouTuber doesn't need to know the details of the subscribers—just that they exist and need to be notified.


🧪 Java Version Relevance

With Java 8+, the Observer pattern becomes cleaner with lambdas. Example:

newsletter.attach(message -> System.out.println("Lambda received: " + message));

📝 Conclusion and Key Takeaways

  • Observer Pattern helps you build loosely-coupled, event-driven systems.
  • It’s excellent for implementing real-time updates and notifications.
  • Java supports this with simple interfaces or reactive streams in modern apps.

✅ Use when: You want to notify multiple objects about state changes.
🚫 Avoid when: Only one object is dependent or the update mechanism is trivial.


❓ FAQ – Observer Pattern in Java

1. What is the Observer Pattern used for?

To notify multiple objects about a state change in one object.

2. Is Observer Pattern still relevant in Java 17+?

Yes, though newer frameworks may offer alternatives (e.g., reactive programming).

3. What are common use cases in backend apps?

Notification systems, loggers, caching mechanisms.

4. Is Java’s built-in Observable class deprecated?

Yes, since Java 9. You should implement your own Subject interface.

5. Can I use lambdas with Observer Pattern?

Yes, especially with functional interfaces in Java 8+.

6. What’s the difference between Observer and EventBus?

EventBus decouples more deeply and supports async events.

7. How to prevent memory leaks in Observer Pattern?

Use weak references or ensure observers are removed when not needed.

8. Should update() methods throw exceptions?

No. Catch exceptions inside observers to avoid breaking the notification chain.

9. Is Observer Pattern synchronous or asynchronous?

Typically synchronous, but can be made asynchronous.

10. Is Observer good for multi-threaded systems?

Use caution. Observer pattern is not thread-safe by default—synchronize access.