Observer Pattern in Java – A Practical Guide for Developers
Fullstack Software Engineer.
What Is the Observer Pattern?
The Observer pattern is a behavioral design pattern where an object, called the Subject, maintains a list of dependents, called Observers, and notifies them automatically whenever its state changes.
It’s like a “publish-subscribe” model — but simpler and more lightweight.
When Should You Use It?
When multiple parts of your system need to respond to a single event or change
When you want to decouple the event source from the reacting components
In UI updates, data synchronization, and notification systems
Real-Life Analogy
Imagine a stock price monitor.
You (the observer) subscribe to a stock (the subject).
When the stock price changes, you're notified instantly.
Components
| Role | Responsibility |
| Subject | Holds the state and notifies observers |
| Observer | Reacts to updates from the subject |
| ConcreteX | Your real objects in the system |
Real-World Use Case 1: Order Tracking Notifications
Problem:
You have an e-commerce app. When an order is shipped, you want to:
Notify the customer by email
Notify the warehouse
Send push notification to the app
All these depend on one event: order shipped.
Without Observer Pattern (Tight Coupling)
public class OrderService {
public void shipOrder() {
System.out.println("Shipping order...");
// notify each service manually
System.out.println("Email sent to customer.");
System.out.println("Warehouse notified.");
System.out.println("Push notification sent.");
}
}
Problems:
Hardcoded dependencies
Adding/removing notifications requires changing this class
No separation of concerns
With Observer Pattern (Loosely Coupled)
Step 1: Define Observer
public interface Observer {
void update(String event);
}
Step 2: Implement Observers
public class EmailNotifier implements Observer {
public void update(String event) {
System.out.println("Email to customer: " + event);
}
}
public class WarehouseNotifier implements Observer {
public void update(String event) {
System.out.println("Warehouse updated: " + event);
}
}
public class PushNotifier implements Observer {
public void update(String event) {
System.out.println("Push to app: " + event);
}
}
Step 3: Subject
public class OrderSubject {
private List<Observer> observers = new ArrayList<>();
public void registerObserver(Observer o) {
observers.add(o);
}
public void notifyObservers(String event) {
for (Observer o : observers) {
o.update(event);
}
}
public void shipOrder() {
System.out.println("Shipping order...");
notifyObservers("Order has been shipped.");
}
}
Step 4: Client Usage
public class Main {
public static void main(String[] args) {
OrderSubject order = new OrderSubject();
order.registerObserver(new EmailNotifier());
order.registerObserver(new WarehouseNotifier());
order.registerObserver(new PushNotifier());
order.shipOrder();
}
}
🎯 Now it's clean, extensible, and decoupled!
Real-World Use Case 2: Live Score Update System
Scenario:
You’re building a live football match app. When the score updates:
The mobile app updates the scoreboard
A web dashboard updates in real-time
A backend analytics system stores the update
Perfect fit for the Observer pattern.
Each component subscribes to a ScoreService, and gets notified whenever the score changes.
Before and After – Observer in Action
Before (Without Observer)
public class ScoreService {
public void updateScore(String newScore) {
System.out.println("Score updated: " + newScore);
System.out.println("UI updated.");
System.out.println("Analytics recorded.");
System.out.println("Dashboard refreshed.");
}
}
👎 Hardcoded logic = hard to test, extend, or maintain.
After (With Observer)
public interface ScoreObserver {
void onScoreUpdate(String score);
}
public class ScoreUI implements ScoreObserver {
public void onScoreUpdate(String score) {
System.out.println("UI shows new score: " + score);
}
}
public class AnalyticsService implements ScoreObserver {
public void onScoreUpdate(String score) {
System.out.println("Analytics logs score: " + score);
}
}
public class ScoreDashboard implements ScoreObserver {
public void onScoreUpdate(String score) {
System.out.println("Dashboard updates with score: " + score);
}
}
public class ScoreSubject {
private List<ScoreObserver> observers = new ArrayList<>();
public void register(ScoreObserver observer) {
observers.add(observer);
}
public void updateScore(String score) {
for (ScoreObserver observer : observers) {
observer.onScoreUpdate(score);
}
}
}
Client code:
ScoreSubject subject = new ScoreSubject();
subject.register(new ScoreUI());
subject.register(new AnalyticsService());
subject.register(new ScoreDashboard());
subject.updateScore("2 - 1");
Benefits Recap
| Without Observer | With Observer |
| Hardcoded logic | Dynamic and flexible |
| Difficult to test | Easily testable |
| Breaks Open/Closed Principle | Follows SOLID |
| Tight coupling | Loose coupling |
| Manual tracking of changes | Automatic and reactive updates |
When NOT to Use Observer
When event flow is simple and fixed
When performance/memory is critical (too many observers = heavy)
When you need centralized control (Event Bus or messaging might fit better)
Real Java Use Cases
ActionListenerin Swing/JavaFXPropertyChangeListenerin beansSpring ApplicationEventPublisherand@EventListenerIntegration with message brokers like Kafka or RabbitMQ (conceptually similar)
Summary
| Concept | Description |
| Pattern Type | Behavioral |
| Key Purpose | Decouple event source from its reactions |
| Common Use | Notifications, UI updates, data sync |
| Real Value | Loose coupling, scalability, cleaner architecture |



