Observer Pattern
Observer Pattern
The Observer pattern defines a one-to-many dependency between objects: when one object (the subject or publisher) changes state, all of its dependents (observers or listeners) are notified automatically. This is the foundational pattern behind every event-driven system you have ever used — from Swing listeners and Android callbacks to Spring application events and reactive streams.
The Problem It Solves
Without Observer, a subject that needs to notify others must hold hard-coded references to every interested party. Add a new consumer and you modify the subject — a clear violation of the Open/Closed Principle. Observer decouples the producer of events from the consumers, so each side can evolve independently.
Classic GoF Structure in Java
The minimal contract requires two interfaces and one concrete subject:
A concrete subject stores its observers in a list and fans out each notification:
List.copyOf(observers) before the loop prevents ConcurrentModificationException when an observer unregisters itself in reaction to being called — a very common real-world scenario.
A Realistic Example: Order Processing
Imagine an e-commerce service where placing an order must trigger multiple side effects — sending a confirmation email, updating inventory, and writing an analytics event. With Observer each concern registers independently:
The OrderService that calls notifyObservers knows nothing about email, inventory, or analytics. Add a new side effect by writing a new class — zero changes to existing code.
Type-Safe, Topic-Filtered Event Bus
Production systems rarely use a single observer list. A common improvement maps event types to separate listener lists, avoiding the instanceof dance inside every observer:
Using Consumer<T> with lambdas removes the need to write separate observer classes for simple reactions — much closer to how modern Java frameworks handle events internally.
Java's Built-in Observer Support
Java has shipped Observer support in several forms over the years:
java.util.Observer/java.util.Observable— deprecated since Java 9. Avoid in new code: thread safety is broken and the design is inflexible.- java.beans PropertyChangeSupport — still used in desktop/Swing code for property-change notifications.
- Flow API (
java.util.concurrent.Flow) — Java 9+ reactive streams:Publisher,Subscriber,Subscription,Processor. Back-pressure aware; the modern foundation for reactive programming. - Spring ApplicationEvent — the Spring Framework wraps Observer behind
ApplicationEventPublisher/@EventListener, giving you declarative, async-capable event handling with DI.
java.util.Observable. It was deprecated in Java 9 for good reasons: the notification is not thread-safe, and its design couples observers to a concrete base class rather than an interface. Use the Flow API, a purpose-built event bus, or a framework like Spring Events instead.
Threading and Memory Considerations
Two issues catch engineers off guard in production:
- Thread safety. If observers are registered or removed from multiple threads, the list must be guarded. Replace
ArrayListwithCopyOnWriteArrayListfor a lock-free, thread-safe alternative that naturally solves the snapshot problem too. - Memory leaks. A subject that holds strong references to observers keeps them alive indefinitely. UI frameworks (Swing, JavaFX, Android) are notorious for this: registering a listener in an activity/fragment and never unregistering it holds the entire view hierarchy in memory. Always pair
addObserverwith a correspondingremoveObserverin a lifecycle teardown method.
Trade-offs and When to Use Observer
- Use it when multiple unrelated components must react to state changes and you want to keep the publisher ignorant of its consumers.
- Watch out for cascade notification storms — an observer that triggers another event can create hard-to-debug chains. Log event dispatches in development.
- Prefer typed buses or frameworks (Spring Events, Guava EventBus, RxJava) over hand-rolled string-keyed buses in production — they add compile-time safety and async support.
- Consider reactive streams (Project Reactor, RxJava) when you need back-pressure, composition, or complex async pipelines; Observer by itself is fire-and-forget.
Summary
The Observer pattern is the engine of event-driven architecture. Its essence is a contract between a publisher that does not know who is listening and any number of listeners that do not know about each other. In modern Java this manifests as lambda-based event buses, the Flow API, and framework-level abstractions like Spring Events. The key professional skills are: keeping the observer list thread-safe, preventing memory leaks through disciplined unregistration, and knowing when to reach for a higher-level reactive abstraction instead of rolling your own bus.