Design Pattern: Observer Pattern in Java

The Observer Pattern is a behavioral design pattern in software engineering. It defines a one-to-many relationship between objects: when the state of one object (the subject) changes, all its dependents (the observers) are automatically notified. This pattern enables components to remain loosely coupled. The subject does not need to know the details of its observers, and observers can be added or removed dynamically.


Interfaces

At the core of the Observer Pattern are three interfaces (or roles).

UML Class Diagram showing the Subject, Observer and Change interfaces, and their relationships.
UML Class Diagram showing the Subject, Observer and Change interfaces, and their relationships.

The Subject maintains a list of observers and notifies them whenever its state changes.

public interface Subject<T> {
    void attach(Observer<T> observer);
    void detach(Observer<T> observer);
}

The Observer reacts to updates from the subject. Each observer implements a single method, typically called update, that receives a Change.

@FunctionalInterface
public interface Observer<T> {
    void update(Change<T> change);
}

The Change encapsulates information about what has changed, allowing observers to respond appropriately.

Java
public interface Change<T> {
    T oldValue();
    T newValue();
}

This separation keeps the system loosely coupled: the subject does not depend on any specific observer, only on the Observer interface.

Abstract Classes

For better code reuse, consider defining an abstract class to capture the common logic shared by every Subject.

Java
public abstract class AbstractSubject<T> implements Subject<T> {
    private final List<Observer<T>> observers;

    protected AbstractSubject() {
        this.observers = new ArrayList<>();
    }

    public final void attach(Observer<T> observer) {
        observers.add(observer);
    }

    public final void detach(Observer<T> observer) {
        observers.remove(observer);
    }

    protected final void notifyObservers(Change<T> change) {
        observers.forEach(observer -> observer.update(change));
    }
}

With that in mind, let’s see the Observer Pattern in practice.


Case Study: Real-Time Stock Prices

To see how the Observer Pattern works in practice, let’s model a real-time stock price system. Each Stock acts as a Subject that notifies multiple Observers whenever its price changes.

First, let’s expand the previous UML class diagram to include concrete classes:

  • Stock (Subject): has a symbol and current price
  • StockPriceLogger (Observer): logs all stock prices to PrintStream
  • StockPriceAlerter (Observer): logs when price goes below threshold to PrintStream
  • StockPriceChange (Change): the stock price change event
  • StockExchangeSimulator: the application entry point (main)
UML Class Diagram showing a Real-Time Stock Price System using the Observer Pattern
UML Class Diagram showing a Real-Time Stock Price System using the Observer Pattern

Subjects

Our Stock (Subject) notifies its registered observers when its price changes.

Java
public class Stock extends AbstractSubject<Double> {
    private final String symbol;
    private Double price;

    public Stock(String symbol, Double initialPrice) {
        this.symbol = symbol;
        this.price = initialPrice;
    }

    public String getSymbol() {
        return symbol;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        double oldPrice = this.price;
        double newPrice = price;
        this.price = newPrice;
        notifyObservers(new PriceChange(oldPrice, newPrice));
    }
}

Observers

The StockPriceLogger logs the old and new value to PrintStream.

Java
public final class StockPriceLogger implements Observer<Double> {
    private final PrintStream out;

    public StockPriceLogger(PrintStream out) {
        this.out = out;
    }

    @Override
    public void update(Change<Double> change) {
        out.printf("Price changed: %4.2fs -> %4.2f\n",
            change.oldValue(),
            change.newValue());
    }
}

The StockPriceAlerter logs the new value to PrintStream if it’s below its threshold.

Java
public final class StockPriceAlerter implements Observer<Double> {
    private final PrintStream out;
    private final Double threshold;

    public StockPriceAlerter(PrintStream out, Double threshold) {
        this.out = out;
        this.threshold = threshold;
    }

    @Override
    public void update(Change<Double> change) {
        if (change.newValue() >= threshold) {
            return;
        }
        out.printf("[Alert] Price below threshold: %s\n", change.newValue());
    }
}

Changes

The PriceChange is just a record with the old and new Double value.

Java
public record PriceChange(Double oldValue, Double newValue)
    implements Change<Double> {}

Main

And finally, our main wires it all together:

  1. Creates a list of stock subjects
  2. Creates the alerter and logger observers
  3. Attaches the observers to each stock
  4. Infinite loop that simulates stock price changes and updates the subjects
Java
public class StockExchangeSimulator {
    public static void main(String[] args) throws InterruptedException {
        Random random = new Random();

        // Add stocks
        List<Stock> stocks = new ArrayList<>();
        stocks.add(new Stock("AAPL", 100.00));

        // Set up observers
        StockPriceAlerter alerter = new StockPriceAlerter(System.out, 98.00);
        StockPriceLogger logger = new StockPriceLogger(System.out);
        stocks.forEach(stock -> stock.attach(alerter));
        stocks.forEach(stock -> stock.attach(logger));

        // Run app
        while (true) {
            Thread.sleep(1000);
            stocks.forEach(stock -> {
                double newPrice = stock.getPrice() + random.nextDouble(1) - 0.67;
                stock.setPrice(newPrice);
            });
        }
    }
}

Java’s Built-In Observer Pattern Implementation

Instead of rolling out your own interfaces, you could also use Java’s built-in Observer Pattern implementation from javax.beans.*

Mapping:

  • Subject → PropertyChangeSupport
  • Observer → PropertyChangeListener
  • Change → PropertyChangeEvent

This is how the previous Real-Time Stock Price application would look like (changes in blue):

UML Class Diagram of Real-Time Stock Application refactored to use javax.beans.PropertyChange*
UML Class Diagram of Real-Time Stock Application refactored to use javax.beans.PropertyChange*

Whenever you use any information, text, code, or images from this guide, always reference back to it as the source: www.Sorn.dev