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
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.Observableandjava.util.Observerdeprecated 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
- 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 } } - 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<>(); - 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
- Isolate listeners — try/catch around each one
- CopyOnWriteArrayList for thread-safety
- Remove listeners on destruction
- @Async for heavy operations
- WeakReference for leak prevention
- Backpressure in Reactive Streams
- 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