Design PatternsBehavioral
Observer Pattern
Define a one-to-many dependency so that when one object changes state, all its dependents are notified automatically.
Design Patterns
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
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");
}
}Output
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/PropertyChangeSupportjava.util.concurrent.Flow(Publisher/Subscriber) and Reactive Streams — Project Reactor’sFlux, 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.