Observer Pattern
Notify dependents automatically when one object changes state. A foundational behavioral pattern — the backbone of event-driven systems, pub/sub architectures, and reactive programming.
Why Observer Exists
You are building a stock trading platform. A StockPrice object holds the current price of a ticker symbol. Multiple parts of the system care about price changes: a charting widget needs to redraw, a portfolio tracker needs to recalculate totals, an alert service needs to check thresholds, and a logging service needs to record the history. The naive approach: the StockPrice class directly calls each dependent.
What goes wrong:
- Tight coupling —
StockPricedepends on every consumer class; changing any consumer’s API forces edits toStockPrice - Open/Closed violation — adding or removing a consumer means editing
setPrice(); the class is never closed for modification - No runtime flexibility — you can’t subscribe or unsubscribe a consumer at runtime (e.g. user closes the chart widget)
- Untestable — testing
StockPricerequires instantiating Chart, Portfolio, Alert, and Logger; mocking is painful
This is the problem Observer solves — define a one-to-many dependency so that when the subject (StockPrice) changes state, all registered observers are notified automatically, without the subject knowing who they are or what they do.
What Is Observer?
Observer is a behavioral pattern that defines a one-to-many dependency between objects: when one object (the subject) changes state, all its dependents (the observers) are notified and updated automatically. The subject maintains a list of observers and calls a common update() method on each one whenever something interesting happens. Observers can subscribe and unsubscribe at runtime — the subject doesn’t know or care what the observers do with the notification.
— Gamma, Helm, Johnson, Vlissides (1994)
Key insight: Observer inverts the dependency. Without the pattern, the subject calls each consumer by name (tight coupling). With Observer, the subject calls a generic interface (Observer.update()) and has no idea who is listening. The observers depend on the subject — not the other way around. This is why Observer is the foundation of every event system: DOM events, Java Swing listeners, RxJava, React state, Kafka consumers, and message queues all trace back to this pattern.
List<Observer> — not a list of concrete classessubscribe() and unsubscribe() allow runtime registrationobserver.update(data)Participants & Structure
| Participant | Role | In the Analogy |
|---|---|---|
| Subject (Publisher) | Holds the state of interest and a list of observers. Provides subscribe(), unsubscribe(), and notifyObservers() methods. When state changes, iterates the list and calls update() on each observer. | The newspaper publisher — maintains a subscriber list and distributes each new edition. |
| Observer (Subscriber) | An interface with a single method: update(data). Every concrete observer implements this to react to state changes in its own way. | The subscription contract — “you will receive a paper.” |
| Concrete Subject | A specific implementation that holds domain state (e.g. stock price). Calls notifyObservers() whenever that state changes. | The New York Times — a specific publisher with specific content. |
| Concrete Observer | Implements update() with domain-specific logic. Knows nothing about other observers. Can subscribe/unsubscribe at any time. | Individual subscribers — one reads sports, another clips coupons, a third wraps fish. |
- In the push model, the subject sends the changed data inside
update(data)— observers get everything they need in one call. - In the pull model,
update()passes only a reference to the subject — observers then query the specific fields they care about. - Push is simpler; Pull avoids sending data observers don’t need. Both are valid Observer implementations.
The Pattern In Motion
Scenario: A stock price feed for AAPL. Three observers subscribe: a chart widget, a portfolio tracker, and an alert service. When the price changes, all three are notified automatically.
StockPriceFeed feed = new StockPriceFeed("AAPL")List<Observer>. No one is listening yet.feed.subscribe(chart), feed.subscribe(portfolio), feed.subscribe(alert)Observer.feed.setPrice(182.50)setPrice() stores the new price, then calls notifyObservers(), which loops through the list and calls observer.update("AAPL", 182.50) on each.ChartWidget redraws the candlestick. PortfolioTracker recalculates the total value. AlertService checks if the price crossed a threshold. None knows about the others.feed.unsubscribe(chart)setPrice() notifies only portfolio and alert. No code changes โ just a runtime call.- The UML (S03) shows who participates. The sequence diagram above shows who calls whom in what order.
- For Observer, the key runtime insight is:
setPrice()triggersnotifyObservers(), which iterates the list and dispatchesupdate()to each observer sequentially.
You Already Use This
Observer is arguably the most pervasive pattern in the JDK. Every event listener, every property change notification, and every reactive stream is an Observer under the hood.
java.util.EventListener — the root marker interface for all AWT/Swing listeners. ActionListener, MouseListener, KeyListener are all Observer interfaces. A JButton (subject) holds a list of ActionListeners and calls actionPerformed(e) on each when clicked. java.beans.PropertyChangeListener — a generic Observer for JavaBeans. Call addPropertyChangeListener() on any bean; when a property changes, propertyChange(PropertyChangeEvent) fires on all listeners. Used heavily in Swing models and Spring framework internals. java.util.concurrent.Flow (Java 9+) — the reactive streams API built into the JDK. Flow.Publisher is the subject; Flow.Subscriber is the observer. Supports backpressure via request(n). This is Observer scaled for async, non-blocking systems. java.util.Observable / java.util.Observer — the original JDK Observer from Java 1.0. Deprecated since Java 9 because Observable is a class (not an interface), forcing single inheritance, and it isn’t thread-safe. Use PropertyChangeListener or Flow instead. java.util.Observable is deprecated: - It’s a class, not an interface — your subject can’t extend anything else.
- It’s not thread-safe. Its
setChanged()/notifyObservers()API is clunky. - Modern alternatives:
PropertyChangeSupportfor synchronous bean events,Flow.Publisherfor reactive streams, or simply roll your ownList<Observer>(it’s ~10 lines of code).
Build It Once
Domain: Stock Price Feed. A StockPriceFeed subject notifies subscribed observers (chart, portfolio, alert) whenever the price changes. Observers subscribe/unsubscribe at runtime.
- Notice the
notifyObservers()method iterates overnew ArrayList<>(observers)— a copy of the list. - If you iterate the original list and an observer unsubscribes during notification, you get a
ConcurrentModificationException. - Always iterate a snapshot when observers might unsubscribe in response to an event.
Common Mistakes
subscribe() with unsubscribe() in a lifecycle callback (e.g. onClose(), dispose()). Or use WeakReference<Observer> so the GC can reclaim unused observers.
- If an observer calls
unsubscribe(this)inside its ownupdate(), and the subject iterates the original list, you get aConcurrentModificationException. - Fix: Iterate a snapshot:
for (Observer o : new ArrayList<>(observers)), or useCopyOnWriteArrayList.
- If the subject fires
notifyObservers()in the middle of a multi-step state change, observers see a half-updated state. - Fix: Complete all state changes before calling
notifyObservers(). Set all fields first, then notify once.
update(): - If an observer does expensive I/O (database write, HTTP call) inside
update(), it blocks all subsequent observers. - The subject’s
setPrice()hangs until the slowest observer finishes. - Fix: Keep
update()lightweight — enqueue work to a background thread, or use an async Observer pattern.
When To Use Observer
- A change in one object must trigger updates in multiple other objects, and you don’t know how many or which ones at compile time
- You need loose coupling — the subject should not depend on the concrete observer classes
- Observers need to subscribe and unsubscribe at runtime (e.g. UI widgets opening and closing)
- You are building an event-driven system — GUI events, message queues, reactive streams, webhooks
- You want to follow the Open/Closed Principle — add new observers without modifying the subject
- There is only one dependent — a direct method call is simpler than setting up subscribe/notify machinery
- Observer ordering matters — Observer doesn’t guarantee notification order; use Chain of Responsibility or a pipeline instead
- You need bidirectional communication — if objects need to talk to each other (not just listen), use Mediator
- Notifications cascade — Observer A updates, which triggers Observer B, which triggers Observer A again — infinite loop; restructure with Mediator or event bus with deduplication
| Pattern | Communication | Coupling | When to pick it |
|---|---|---|---|
| Observer ← this | One-to-many broadcast | Subject → Observer interface only | Fan-out notifications: one event, many listeners |
| Mediator | Many-to-many via hub | All objects → Mediator | Complex inter-object communication (chat rooms, UI forms) |
| Command | Request encapsulated as object | Invoker → Command interface | Undo/redo, queuing, logging requests |
| Chain of Responsibility | One request, one handler in a chain | Handler → next Handler | Request must be handled by exactly one of many handlers |
Problems To Solve
Observer problems test whether you can decouple a subject from its dependents, manage subscribe/unsubscribe lifecycles, and handle edge cases like notification ordering and concurrent modification.
| Difficulty | Problem | Key Insight |
|---|---|---|
| Easy | Weather Station Build a WeatherStation subject that holds temperature, humidity, and pressure. Three displays subscribe: CurrentConditions, Statistics (running average), and Forecast (rising/falling pressure). When setMeasurements() is called, all displays update. | Tests the basic subscribe/notify loop. The interesting part: Statistics must maintain state across updates (running average), proving that each observer can have its own internal logic. Use push model — pass all 3 values in update(). |
| Medium | Event Bus with Topic Filtering Build an EventBus where observers subscribe to specific topics (e.g. "order.created", "order.shipped") instead of receiving all events. Publishing an event with topic "order.created" notifies only subscribers of that topic. | The subject holds a Map<String, List<Observer>> instead of a flat list. subscribe(topic, observer) registers per-topic. publish(topic, data) notifies only matching observers. This is how real event buses (Guava EventBus, Spring ApplicationEvent) work under the hood. |
| Medium | Reactive Spreadsheet Cell Build a spreadsheet where cells observe other cells. Cell A1 holds a value. Cell B1 = A1 * 2. Cell C1 = A1 + B1. When A1 changes, B1 recalculates, which triggers C1 to recalculate. Prevent infinite loops if cells form a cycle. | Tests cascading notifications. Each cell is both a subject (notifies dependents) and an observer (recalculates when dependencies change). The hard part: cycle detection. Use a “dirty” flag or topological sort to prevent infinite re-evaluation. |
| Hard | Thread-Safe Observer with Backpressure Build a multi-threaded stock feed where prices arrive on a producer thread and observers run on consumer threads. If an observer is slow, the subject must not block. Implement request(n) backpressure: an observer tells the subject how many updates it can handle. | Tests async Observer + reactive streams. Use ConcurrentLinkedQueue or BlockingQueue per observer. The subject enqueues updates; each observer dequeues at its own pace. request(n) controls queue depth. This is exactly java.util.concurrent.Flow — implementing it from scratch teaches the reactive streams spec. |
- When asked about Observer, the interviewer wants to see: (1) a
Subjectwithsubscribe(),unsubscribe(), andnotifyObservers(); (2) anObserverinterface withupdate(); (3) loose coupling — the subject depends only on the observer interface; (4) runtime flexibility — observers come and go dynamically. - Stand-out answers mention:
ConcurrentModificationExceptionduring notification (iterate a copy), memory leaks from lapsed listeners, the push vs. pull tradeoff, and howjava.util.Observablewas deprecated in favor ofFlow.Publisher.