StackWise
Design PatternsBehavioral

Observer Pattern

Define a one-to-many dependency so that when one object changes state, all its dependents are notified automatically.

beginner 3 min readLesson 21 of 25

Intent

Observer (also publish–subscribe or listener) lets a subject broadcast events to any number of observers that registered interest — without the subject knowing who they are or what they do. Adding a new reaction to an event means adding a new observer, not editing the subject.

Structure

classDiagram
  direction LR
  class EventBus~E~ {
    -List listeners
    +subscribe(Consumer~E~) Subscription
    +publish(E event)
  }
  class OrderService {
    +placeOrder()
  }
  class EmailListener
  class InventoryListener
  class AnalyticsListener
  OrderService --> EventBus : publishes
  EventBus o--> EmailListener : notifies
  EventBus o--> InventoryListener : notifies
  EventBus o--> AnalyticsListener : notifies
sequenceDiagram
  participant S as OrderService
  participant B as EventBus
  participant E as EmailListener
  participant I as InventoryListener
  S->>B: publish(OrderPlaced)
  B->>E: onEvent(OrderPlaced)
  B->>I: onEvent(OrderPlaced)
  Note over S: OrderService knows nothing<br/>about email or inventory

Example: a type-safe event bus

ObserverDemo.javaJava 21
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;

sealed interface OrderEvent permits OrderPlaced, OrderCancelled {}
record OrderPlaced(String orderId, String email, BigDecimal total) implements OrderEvent {}
record OrderCancelled(String orderId, String reason) implements OrderEvent {}

// The subject: keeps a list of observers and notifies them
final class EventBus<E> {
    private final List<Consumer<? super E>> listeners = new CopyOnWriteArrayList<>();

    AutoCloseable subscribe(Consumer<? super E> listener) {
        listeners.add(listener);
        return () -> listeners.remove(listener);           // handle to unsubscribe
    }

    void publish(E event) {
        for (var l : listeners) {
            try { l.accept(event); }
            catch (RuntimeException ex) { System.out.println("  ! listener failed: " + ex.getMessage()); }
        }
    }
}

class OrderService {
    private final EventBus<OrderEvent> events;
    OrderService(EventBus<OrderEvent> events) { this.events = events; }

    void place(String id, String email, String total) {
        System.out.println("Order " + id + " saved");
        events.publish(new OrderPlaced(id, email, new BigDecimal(total)));
    }
    void cancel(String id) { events.publish(new OrderCancelled(id, "customer request")); }
}

class Main {
    public static void main(String[] args) throws Exception {
        var bus = new EventBus<OrderEvent>();

        bus.subscribe(e -> { if (e instanceof OrderPlaced p) System.out.println("  ✉ email " + p.email()); });
        bus.subscribe(e -> {
            switch (e) {
                case OrderPlaced p -> System.out.println("  📦 reserve stock for " + p.orderId());
                case OrderCancelled c -> System.out.println("  📦 release stock for " + c.orderId());
            }
        });
        AutoCloseable analytics = bus.subscribe(e -> System.out.println("  📈 track " + e.getClass().getSimpleName()));
        bus.subscribe(e -> { throw new IllegalStateException("flaky webhook"); });

        var orders = new OrderService(bus);
        orders.place("A-100", "ada@example.com", "59.90");
        analytics.close();                                      // unsubscribe
        orders.cancel("A-100");
    }
}

Observer in Spring

Spring’s application events are Observer with dependency injection:

public record OrderPlaced(Long orderId, String email) {}

@Service
class OrderService {
    private final ApplicationEventPublisher events;

    @Transactional
    public void place(Order order) {
        repository.save(order);
        events.publishEvent(new OrderPlaced(order.getId(), order.getEmail()));
    }
}

@Component
class ConfirmationEmailListener {
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)  // only if the tx commits
    @Async
    public void on(OrderPlaced event) { mailer.sendConfirmation(event.email()); }
}

Where you’ll see it

  • Swing/AWT listeners (ActionListener), JavaFX properties
  • java.beans.PropertyChangeListener / PropertyChangeSupport
  • java.util.concurrent.Flow (Publisher/Subscriber) and Reactive Streams — Project Reactor’s Flux, RxJava — observer with back-pressure
  • Spring ApplicationEvents, @EventListener, @TransactionalEventListener; Spring Modulith’s event publication registry
  • Message brokers — Kafka topics are observer across processes
Finished this lesson?

Mark it complete to track your progress through Design Patterns.