Question 11 · Section 2

How is Observer Implemented in Java?

4. @Async for heavy operations 5. WeakReference for leak prevention 6. Backpressure in Reactive Streams 7. Spring Events for enterprise applications

Language versions: English Russian Ukrainian

Junior Level

Observer is a pattern where one object (Subject) notifies other observers (Observers) about changes.

Simple analogy: YouTube channel subscription. When the author publishes a video, all subscribers get a notification.

Example:

// Observer
interface NewsListener {
    void onNewVideo(String title);
}

// Subject (publisher)
class YouTuber {
    private List<NewsListener> listeners = new ArrayList<>();

    public void subscribe(NewsListener listener) {
        listeners.add(listener);
    }

    public void unsubscribe(NewsListener listener) {
        listeners.remove(listener);
    }

    public void publishVideo(String title) {
        System.out.println("New video: " + title);
        // Notify all subscribers
        for (NewsListener listener : listeners) {
            listener.onNewVideo(title);
        }
    }
}

// Usage
YouTuber youtuber = new YouTuber();
youtuber.subscribe(title -> System.out.println("I'll watch: " + title));
youtuber.publishVideo("Java Tutorial");

Where used:

  • UI buttons and events
  • News subscriptions
  • Reactive systems

Middle Level

java.util.Observable and java.util.Observer deprecated since Java 9. Alternatives: PropertyChangeListener, Flow API (Java 9+), Spring Events.

Java Bean PropertyChangeListener

// Built-in Observer implementation in Java
public class Model {
    private final PropertyChangeSupport support = new PropertyChangeSupport(this);
    private String name;

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        support.addPropertyChangeListener(listener);
    }

    public void setName(String name) {
        String old = this.name;
        this.name = name;
        support.firePropertyChange("name", old, name);  // Notification!
    }
}

// Usage
Model model = new Model();
model.addPropertyChangeListener(evt ->
    System.out.println(evt.getPropertyName() + ": " + evt.getOldValue() + " -> " + evt.getNewValue())
);
model.setName("New Name");

Functional Observer (Java 8+)

// Instead of interface — lambdas
public class EventSource {
    private final List<Consumer<String>> listeners = new ArrayList<>();

    public void onEvent(Consumer<String> listener) {
        listeners.add(listener);
    }

    public void emit(String event) {
        listeners.forEach(listener -> listener.accept(event));
    }
}

// Usage
EventSource source = new EventSource();
source.onEvent(e -> System.out.println("Listener 1: " + e));
source.onEvent(e -> System.out.println("Listener 2: " + e));
source.emit("Hello!");

Typical Problems

  1. Exception in listener stops all others
    // If one listener throws exception — others won't get notified
    for (NewsListener listener : listeners) {
        listener.onNewVideo(title);  // Exception -> loop breaks!
    }
    
    // Isolated listeners
    for (NewsListener listener : listeners) {
        try {
            listener.onNewVideo(title);
        } catch (Exception e) {
            log.error("Listener failed", e);
            // Continue notifying others
        }
    }
    
  2. ConcurrentModificationException
    // Listener removes itself during notification
    for (NewsListener listener : listeners) {
        listener.onNewVideo(title);  // Inside: listeners.remove(this) -> Exception!
    }
    
    // Copy of list or CopyOnWriteArrayList
    private final List<NewsListener> listeners = new CopyOnWriteArrayList<>();
    
  3. Memory leak
    // Listener not removed -> leak
    model.addPropertyChangeListener(listener);
    // Forgot to remove -> model holds listener forever
    
    // Always remove
    model.removePropertyChangeListener(listener);
    

Senior Level

Event-driven Architecture

// Spring Events — Observer on steroids
@Component
public class OrderService {
    private final ApplicationEventPublisher publisher;

    public void createOrder(Order order) {
        // Save order
        orderRepository.save(order);

        // Publish event
        publisher.publishEvent(new OrderCreatedEvent(order));
    }
}

// Listener
@Component
public class NotificationService {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        sendEmail(event.getOrder().getEmail());
    }
}

// Async listener
@Component
public class AnalyticsService {
    @Async
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        trackAnalytics(event.getOrder());  // Doesn't block main thread!
    }
}

Reactive Streams (Java 9+)

// Flow API — modern Observer implementation
public class NewsPublisher implements Flow.Publisher<String> {
    private final List<Flow.Subscriber<String>> subscribers = new ArrayList<>();

    @Override
    public void subscribe(Flow.Subscriber<String> subscriber) {
        subscribers.add(subscriber);
        subscriber.onSubscribe(new Flow.Subscription() {
            public void request(long n) { /* backpressure */ }
            public void cancel() { subscribers.remove(subscriber); }
        });
    }

    public void publish(String news) {
        subscribers.forEach(s -> s.onNext(news));
    }
}

Thread Safety

// Multithreading problems in Observer
public class EventSource {
    // ArrayList not thread-safe
    private List<Consumer<String>> listeners = new ArrayList<>();

    // CopyOnWriteArrayList for frequent reads, rare writes
    private List<Consumer<String>> listeners = new CopyOnWriteArrayList<>();

    // Or synchronized
    public synchronized void addListener(Consumer<String> listener) {
        listeners.add(listener);
    }

    // AtomicReference for singleton listener
    private final AtomicReference<Consumer<String>> listener = new AtomicReference<>();
}

WeakReference Observers

// Preventing leaks through weak references
public class WeakEventSource {
    private final List<WeakReference<Consumer<String>>> listeners = new ArrayList<>();

    public void addListener(Consumer<String> listener) {
        listeners.add(new WeakReference<>(listener));
    }

    public void emit(String event) {
        // Copy list to avoid ConcurrentModificationException
        // if listener adds a new listener during notify
        new ArrayList<>(listeners).forEach(ref -> {
            Consumer<String> l = ref.get();
            if (l != null) l.accept(event);
        });
    }
}

Production Experience

Real scenario: Blocking listener killed API

  • Synchronous @EventListener sent email
  • Email service was slow -> entire API waited
  • Solution: @Async for listener
  • Result: API < 100ms stable

Best Practices

  1. Isolate listeners — try/catch around each one
  2. CopyOnWriteArrayList for thread-safety
  3. Remove listeners on destruction
  4. @Async for heavy operations
  5. WeakReference for leak prevention
  6. Backpressure in Reactive Streams
  7. Spring Events for enterprise applications

Senior Summary

  • Observer = foundation of event-driven architecture
  • Spring @EventListener = production-ready implementation
  • Reactive Streams = backpressure + non-blocking
  • Thread-safety: CopyOnWriteArrayList or synchronized
  • Async listeners don’t block the publisher
  • WeakReference prevents leaks
  • Isolate each listener (try/catch)

Interview Cheat Sheet

Must know:

  • java.util.Observable and Observer deprecated since Java 9, alternatives: PropertyChangeListener, Flow API, Spring Events
  • Spring @EventListener — production-ready Observer: synchronous and @Async for non-blocking listeners
  • Flow API (Java 9+) — Reactive Streams with backpressure: Publisher, Subscriber, Subscription
  • Thread-safety: CopyOnWriteArrayList for frequent reads/rare writes, or synchronized
  • Exception in one listener must not stop others — try/catch around each one
  • WeakReference Observers prevent memory leaks when removal is forgotten
  • ConcurrentModificationException when removing listener during notification — solved by copying the list

Common follow-up questions:

  • Why are Observable/Observer deprecated? — Outdated design, no backpressure support, synchronous
  • How do Spring Events solve Observer problems? — @EventListener + ApplicationEventPublisher, @Async for non-blocking
  • What is backpressure in Reactive Streams? — Mechanism allowing subscriber to control data receive rate
  • How to avoid memory leaks in Observer? — Always remove listeners, use WeakReference

Red flags (DO NOT say):

  • “I use java.util.Observable” — deprecated since Java 9
  • “Listeners can’t cause deadlock” — synchronous listener + lock = deadlock
  • “Concurrent modification is not a problem” — ConcurrentModificationException on removal during notify
  • “All listeners must be synchronous” — @Async for heavy operations (email, analytics)

Related topics:

  • [[15. What is Iterator pattern]] — iterating over listeners
  • [[02. What pattern categories exist]] — Behavioral patterns
  • [[14. What Proxy types exist]] — Proxy in Spring Events
  • [[10. When to use Strategy]] — algorithm swapping
  • [[13. Difference between State and Strategy]] — behavioral patterns