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

RoleResponsibility
SubjectHolds the state and notifies observers
ObserverReacts to updates from the subject
ConcreteXYour 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 ObserverWith Observer
Hardcoded logicDynamic and flexible
Difficult to testEasily testable
Breaks Open/Closed PrincipleFollows SOLID
Tight couplingLoose coupling
Manual tracking of changesAutomatic 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/JavaFX

  • PropertyChangeListener in beans

  • Spring ApplicationEventPublisher and @EventListener

  • Integration with message brokers like Kafka or RabbitMQ (conceptually similar)


Summary

ConceptDescription
Pattern TypeBehavioral
Key PurposeDecouple event source from its reactions
Common UseNotifications, UI updates, data sync
Real ValueLoose coupling, scalability, cleaner architecture
1
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.