Observer Pattern in Java – A Practical Guide for Developers

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
ActionListener
in Swing/JavaFXPropertyChangeListener
in beansSpring ApplicationEventPublisher
and@EventListener
Integration 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 |
Subscribe to my newsletter
Read articles from André Felipe Costa Bento directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

André Felipe Costa Bento
André Felipe Costa Bento
Fullstack Software Engineer.