Do you ever wonder how a smartwatch can notify someone miles away when it detects a dangerous heart rhythm? Or how ICU/hospital monitoring systems stream a patient’s vitals to multiple screens and trigger alerts when something goes abnormal like a high temperature, irregular heartbeat, or dropping oxygen level?
In these situations, we usually don’t manually “call an API” every few seconds to check for updates. Instead, updates are pushed automatically (or continuously streamed) to whoever needs them dashboards, mobile apps, nurses’ stations, alerting systems, etc.
This is exactly the kind of problem the Gang of Four (GoF) described with the Observer Pattern.
What Do These Examples Share?
One thing keeps changing: data.
- Temperature changes
- Heart rate changes
- Oxygen saturation changes
- Sensor readings change
The Observer Pattern gives the data source (the thing that changes) a clean way to notify all interested parties (watchers) automatically whenever the value changes.
That’s why it’s called Observer: multiple observers “watch” a subject, and when the subject changes, they get notified
What Is the Observer Pattern?
The intent of this pattern is to define a one-to-many dependency between objects so that when one object changes its state, all its dependents are notified and updated automatically.
We have an Observable, which is our source of data or something which keeps changing.
We have many objects which is called Observers.
The update can use a pull or push mechanism.
Let us try to implement this pattern in Simple Java

Pattern Structure: Class Diagram
Here’s the class structure showing how Observer and Observable work together:
Creating the Observer Interface
In languages like java, the power comes from the interfaces. Like that we have Observer.
interface Observer<T> {
void update(T newValue);
}
The interface has an update() function that gets called when there is a change in state.
The Observable Base Class
abstract class Observable<T> {
private final List<Observer> observers = new ArrayList<>();
public void addObserver(Observer o) {
observers.add(o);
}
public void removeObserver(Observer o) {
observers.remove(o);
}
// Protected: only subclasses can trigger notifications
protected void notifyObservers(T value) {
for (Observer o : observers) {
o.update(T);
}
}
}
The abstract class Observable has three methods , we can add more methods
addObserver(Observer observe) – This will add list of objects who are subscribing
removeObserver(Observer observe) – This will remove list of object who are subscribing
notifyObservers(T value) – This method sends the updates
The TemperatureSensor Observable
class TemperatureSensor extends Observable<Integer>{
private Integer temperature;
public void setTemperature(Integer newTemp) {
this.temperature = newTemp;
notifyObservers(newTemp); // notify everyone when it changes
}
}
The method set Temperature is called new temperature is notified to subscribing objects.
Putting It All Together: Demo
public class ObserverDemo {
public static void main(String[] args) {
TemperatureSensor sensor = new TemperatureSensor();
// Observer 1: Dashboard
sensor.addObserver(newTemp ->
System.out.println("Dashboard shows temperature: " + newTemp)
);
// Observer 2: Alert system
sensor.addObserver(newTemp -> {
if (newTemp > 50) {
System.out.println("ALERT! Temperature too high: " + newTemp);
}
});
sensor.setTemperature(40);
sensor.setTemperature(55);
}
}
Common Pitfalls and Challenges
While the Observer Pattern is powerful, it comes with several challenges you should be aware of:
Memory Leaks
If observers are not explicitly removed when they’re no longer needed, they remain in the observer list. This prevents garbage collection and leads to memory leaks. Always call removeObserver() when an observer is done listening.
Unpredictable Notification Order
There’s no guaranteed order in which observers are notified. If one observer depends on another observer’s action, this can lead to bugs. The pattern doesn’t provide any mechanism to control execution order.
Performance Bottlenecks
Notifying many observers synchronously can block the main thread, especially if each observer performs time-consuming operations. In our basic implementation, all notifications happen on the same thread, which can cause lag in responsive applications.
Lack of Thread Safety
Our implementation isn’t thread-safe. If multiple threads add/remove observers or trigger notifications simultaneously, you can get ConcurrentModificationException or other threading issues. Consider using CopyOnWriteArrayList or synchronized blocks for multi-threaded environments.
Cascading Updates
An observer’s update method might modify another observable, which then notifies its observers, creating a chain reaction. Without careful design, this can lead to infinite loops or hard-to-debug behavior.
When to Use the Observer Pattern
The Observer Pattern shines in these scenarios:
- Event-driven systems: GUI applications where buttons, forms, and components need to respond to user actions
- Real-time data monitoring: Stock tickers, sensor dashboards, live sports scores
- MVC architecture: Views observing model changes to update UI automatically
- Pub/Sub messaging: When multiple components need to react to state changes without tight coupling
When NOT to Use the Observer Pattern
Avoid this pattern in these situations:
- Simple one-to-one relationships: If you only have one subscriber, direct method calls are simpler.
- Simple one-to-one relationships: If you only have one subscriber, direct method calls are simpler.
- Order-dependent operations: When the sequence of notifications matters, the Observer pattern isn’t ideal.
- High-frequency updates: If state changes thousands of times per second, the overhead can be significant.
- Performance-critical systems: The indirection and iteration over observers adds latency.
- Complex dependencies: When observers depend on each other, consider other patterns like mediator.
Real-World Implementations
The Observer Pattern is everywhere in modern software development:
- Java
- java.util.Observable and Observer (deprecated in Java 9)
- Spring Framework’s ApplicationEventPublisher
- JavaScript
- DOM event listeners (addEventListener)
- Node.js EventEmitter
- RxJS Observables
- Vue.js and Angular reactivity systems
- Android
- LiveData for lifecycle-aware observables
- Kotlin Flow and StateFlow
- Modern Alternatives
- Reactive Extensions (RxJava, RxJS, RxSwift)
- Google Guava’s EventBus
- Apache Kafka for distributed event streaming
Thread Safety Considerations
Our basic implementation has a critical flaw: it’s not thread-safe. Here’s how to fix it
abstract class ThreadSafeObservable {
// Use CopyOnWriteArrayList for thread-safe operations
private final List> observers = new CopyOnWriteArrayList<>();
public void addObserver(Observer<T> o) {
observers.add(o);
}
public void removeObserver(Observer<T> o) {
observers.remove(o);
}
protected void notifyObservers(T value) {
// CopyOnWriteArrayList ensures thread-safe iteration
for (Observer<T> o : observers) {
o.update(value);
}
}
}
Why CopyOnWriteArrayList?
Thread-safe for concurrent reads and writes
No ConcurrentModificationException during iteration
Best for scenarios where reads are more frequent than writes each modification creates a new copy of the underlying array
Best Practices
Follow these guidelines to use the Observer Pattern effectively:
1. Always Clean Up Observers
Always remove observers when they’re no longer needed to prevent memory leaks. Use try-finally blocks or lifecycle methods to ensure cleanup.
public class Dashboard implements Observer {
private TemperatureSensor sensor;
public void start(TemperatureSensor sensor) {
this.sensor = sensor;
sensor.addObserver(this);
}
public void stop() {
if (sensor != null) {
sensor.removeObserver(this);
}
}
@Override
public void update(Integer temp) {
System.out.println("Temperature: " + temp);
}
}
2. Handle Errors in Observers
One failing observer shouldn’t break the entire notification chain. Wrap update calls in try-catch blocks.
3. Consider Asynchronous Notifications
For long-running observers, use ExecutorService to notify asynchronously and avoid blocking the observable.
4. Document Your Observer Contract
Clearly specify whether observers can modify the observable during updates, whether notifications are synchronous or async, and what thread safety guarantees you provide.
5. Use Weak References for Long-Lived Observables
If your observable lives much longer than observers, consider using WeakReference to allow garbage collection of observers that are no longer in use.



