Your stock monitoring service tracks a price. Ten different components care when it changes: a risk alert, two portfolio trackers, a chart renderer, an audit log. If the price object calls each one directly, it must know about all ten. Add an eleventh component and you modify the stock object — which should know nothing about UI renderers or audit logs. The Observer pattern inverts this: components register their interest, and the stock object notifies whoever is listening without knowing who that is.
This is the backbone of event-driven programming. Every GUI event listener, every reactive stream, every message bus, every Spring application event — they all implement the same Observer structure. Learning it from first principles makes all of those systems immediately legible.
Pattern Structure

Implementation: Stock Price Monitoring
package observer;
/** Observer interface — all subscribers implement this */
public interface StockObserver {
void onPriceChanged(String ticker, double oldPrice, double newPrice);
}
package observer;
import java.util.ArrayList;
import java.util.List;
/**
* Subject (Observable) — maintains the observer list and notifies on change.
* It knows observers exist but knows nothing about their concrete types.
*/
public class StockMarket {
private final String ticker;
private double price;
private final List<StockObserver> observers = new ArrayList<>();
public StockMarket(String ticker, double initialPrice) {
this.ticker = ticker;
this.price = initialPrice;
System.out.println("[Market] " + ticker + " initialised at $" + initialPrice);
}
public void subscribe(StockObserver observer) { observers.add(observer); }
public void unsubscribe(StockObserver observer) { observers.remove(observer); }
public void setPrice(double newPrice) {
double old = this.price;
this.price = newPrice;
System.out.printf("[Market] %s: $%.2f -> $%.2f%n", ticker, old, newPrice);
// Notify all registered observers — push model (we send old and new values)
observers.forEach(o -> o.onPriceChanged(ticker, old, newPrice));
}
}
package observer;
/** Fires an alert when price moves by more than a threshold percentage */
public class AlertObserver implements StockObserver {
private final String name;
private final double thresholdPercent;
public AlertObserver(String name, double thresholdPercent) {
this.name = name; this.thresholdPercent = thresholdPercent;
}
@Override
public void onPriceChanged(String ticker, double oldPrice, double newPrice) {
double change = Math.abs((newPrice - oldPrice) / oldPrice * 100);
if (change >= thresholdPercent) {
System.out.printf(" [ALERT:%s] %s moved %.1f%% — TRIGGERED%n", name, ticker, change);
} else {
System.out.printf(" [Alert:%s] %s moved %.1f%% — within threshold%n", name, ticker, change);
}
}
}
/** Updates portfolio P&L on every price change */
public class PortfolioObserver implements StockObserver {
private final String ownerName;
private final int sharesHeld;
public PortfolioObserver(String ownerName, int sharesHeld) {
this.ownerName = ownerName; this.sharesHeld = sharesHeld;
}
@Override
public void onPriceChanged(String ticker, double oldPrice, double newPrice) {
double gain = (newPrice - oldPrice) * sharesHeld;
System.out.printf(" [Portfolio:%s] %s×%d P&L change: %+.2f%n",
ownerName, ticker, sharesHeld, gain);
}
}
public class Main {
public static void main(String[] args) {
StockMarket aapl = new StockMarket("AAPL", 175.00);
StockObserver alert = new AlertObserver("RiskEngine", 2.0);
StockObserver alice = new PortfolioObserver("Alice", 100);
StockObserver bob = new PortfolioObserver("Bob", 50);
aapl.subscribe(alert);
aapl.subscribe(alice);
aapl.subscribe(bob);
aapl.setPrice(178.50); // +2% — should trigger alert
aapl.setPrice(179.00); // small move
System.out.println("\n-- Bob unsubscribes --");
aapl.unsubscribe(bob);
aapl.setPrice(165.00); // big drop — Bob no longer notified
}
}
Console Output
[Market] AAPL initialised at $175.0
[Market] AAPL: $175.00 -> $178.50
[ALERT:RiskEngine] AAPL moved 2.0% — TRIGGERED
[Portfolio:Alice] AAPL×100 P&L change: +350.00
[Portfolio:Bob] AAPL×50 P&L change: +175.00
[Market] AAPL: $178.50 -> $179.00
[Alert:RiskEngine] AAPL moved 0.3% — within threshold
[Portfolio:Alice] AAPL×100 P&L change: +50.00
[Portfolio:Bob] AAPL×50 P&L change: +25.00
— Bob unsubscribes —
[Market] AAPL: $179.00 -> $165.00
[ALERT:RiskEngine] AAPL moved 7.8% — TRIGGERED
[Portfolio:Alice] AAPL×100 P&L change: -1400.00
=== Demo complete ===
Push vs Pull Notification Models
The example above uses the push model: the subject sends both old and new values to each observer. The alternative is the pull model: the subject sends only a reference to itself (or just notifies with no data), and each observer calls back to get what it needs.
// Pull model — observer fetches what it needs from the subject
interface Observer { void update(StockMarket market); }
class PullAlertObserver implements Observer {
@Override
public void update(StockMarket market) {
double price = market.getCurrentPrice(); // observer pulls the data
// ... compute change based on last known price stored in the observer ...
}
}
// Push is simpler when observers need the same data
// Pull is better when different observers need different data from the subject
// or when sending all data upfront would be wasteful
💡 Observer in the JDK: java.beans.PropertyChangeListener and PropertyChangeSupport are the JDK’s built-in Observer implementation for Java beans. Swing’s entire event model — ActionListener, MouseListener, KeyListener — is Observer. Every addXxxListener() / removeXxxListener() method is subscribe() / unsubscribe(). The deprecated java.util.Observable / java.util.Observer pair was removed in Java 9 because Observable was a class (not an interface), making it awkward to compose with other hierarchies.
Thread Safety
The simple ArrayList of observers is not thread-safe. If one thread calls subscribe() while another is iterating through observers in setPrice(), you get ConcurrentModificationException. The standard fix is to copy the list before iterating, or use CopyOnWriteArrayList:
import java.util.concurrent.CopyOnWriteArrayList;
// Thread-safe: reads iterate a snapshot; writes create a new copy
private final List<StockObserver> observers = new CopyOnWriteArrayList<>();
// No synchronisation needed on the notification loop:
observers.forEach(o -> o.onPriceChanged(ticker, old, newPrice));
// CopyOnWriteArrayList.forEach() operates on a consistent snapshot
// even if subscribe/unsubscribe is called concurrently
✅ Avoid Notification Ordering Dependencies: Observers should be independent — the behaviour of observer B should not depend on observer A having been notified first. Java does not guarantee notification order (even with a list, the order depends on registration order, which callers control). If two observers need ordered coordination, use Mediator or Chain of Responsibility instead. Pure Observer assumes subscribers are independent and commutative.
When to Use Observer
Reach for Observer when: a change in one object requires updating others, and you don’t know how many objects need updating or who they are at design time. You want to decouple the source of events from the handling of events. You need dynamic subscription — components connecting and disconnecting at runtime.
Avoid it when: observers have side effects on each other (observer A’s execution affects what observer B sees — this creates ordering dependency). The notification chain is long and deeply nested — hard to debug and profile. You need reliable guaranteed delivery — plain Observer drops notifications if the subject doesn’t hold a reference to a subscriber.
Runnable Code on GitHub
Full source at ankurm.com/git.app/asmhatre/design-patterns under 03-behavioral/observer/.
javac 03-behavioral/observer/*.java -d out/observer
java -cp out/observer observer.Main
See Also
- Mediator Design Pattern in Java — many-to-many coordination; Observer is one-to-many broadcast from a known subject
- Strategy Design Pattern in Java — also injects behaviour from outside; Strategy swaps algorithms, Observer subscribes to notifications
Further Reading
- Observer — Refactoring.Guru
- Design Patterns: Elements of Reusable Object-Oriented Software — Gamma et al., Chapter 5