Design Patterns

83. Observer Pattern

The Observer pattern defines a one-to-many dependency so that when one object changes state, all its dependents are notified; java.util.Observable is deprecated โ€” use event listeners, PropertyChangeSupport, or a reactive library.

Java 21 (core language)

Explanation

The Observer (or Publish-Subscribe) pattern has a Subject that maintains a list of Observers and notifies them when its state changes. Every GUI framework, event system, and reactive library is built on this idea.

java.util.Observable and java.util.Observer were deprecated in Java 9 because Observable is a class (forces inheritance), not thread-safe by default, and passes events as generic Object rather than typed events. Do not use them.

Modern Java alternatives: (1) Manual observer: simple functional interface list, often sufficient. (2) PropertyChangeSupport (from java.beans): for JavaBean-style property change events. (3) Flow API (Java 9+): reactive streams with backpressure. (4) Event buses (Guava EventBus, Spring ApplicationEventPublisher): for decoupled, cross-module events.

Example

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class ObserverPattern {
    // Modern manual observer: typed, lambda-friendly
    static class EventBus<T> {
        private final List<Consumer<T>> listeners = new ArrayList<>();

        public void subscribe(Consumer<T> listener)   { listeners.add(listener); }
        public void unsubscribe(Consumer<T> listener) { listeners.remove(listener); }
        public void publish(T event) { listeners.forEach(l -> l.accept(event)); }
    }

    record StockPrice(String ticker, double price) {}

    // PropertyChangeSupport: JavaBean style
    static class Temperature {
        private double celsius;
        private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);

        public void addPropertyChangeListener(PropertyChangeListener l) { pcs.addPropertyChangeListener(l); }

        public void setCelsius(double celsius) {
            double old = this.celsius;
            this.celsius = celsius;
            pcs.firePropertyChange("celsius", old, celsius);
        }
        public double getCelsius() { return celsius; }
    }

    public static void main(String[] args) {
        // Manual typed event bus
        EventBus<StockPrice> stockBus = new EventBus<>();

        Consumer<StockPrice> logger    = p -> System.out.printf("LOG:   %s = $%.2f%n", p.ticker(), p.price());
        Consumer<StockPrice> alerter   = p -> { if (p.price() > 200) System.out.println("ALERT: " + p.ticker() + " crossed $200!"); };

        stockBus.subscribe(logger);
        stockBus.subscribe(alerter);

        stockBus.publish(new StockPrice("JAVA", 150.00));
        stockBus.publish(new StockPrice("JAVA", 215.50)); // triggers alert

        stockBus.unsubscribe(alerter);
        stockBus.publish(new StockPrice("JAVA", 100.00)); // no alert (unsubscribed)

        System.out.println();

        // PropertyChangeSupport
        Temperature temp = new Temperature();
        temp.addPropertyChangeListener(evt ->
            System.out.printf("Temp changed: %.1f -> %.1fยฐC%n",
                (double) evt.getOldValue(), (double) evt.getNewValue()));

        temp.setCelsius(20.0);
        temp.setCelsius(25.5);
    }
}

Sources