How to Implement the Observer Pattern Using Java PropertyChangeSupport
Use java.beans.PropertyChangeSupport to manage listener registration and event firing, eliminating manual list management while providing rich change notifications with old and new values.
The Observer pattern decouples subjects from observers, but hand-rolling listener lists adds boilerplate and thread-safety concerns. The iluwatar/java-design-patterns repository demonstrates this pattern in Weather.java and WeatherObserver.java, yet you can modernize the implementation by leveraging the JDK's built-in PropertyChangeSupport class to handle the observer infrastructure.
Why Use PropertyChangeSupport for the Observer Pattern?
The standard implementation in Weather.java maintains a List<WeatherObserver> and manually iterates to notify observers. Switching to PropertyChangeSupport offers three concrete advantages:
- Reduced boilerplate: The support class manages the listener list, synchronization, and iteration logic internally.
- Rich event metadata: Observers receive
PropertyChangeEventobjects containing the property name, old value, and new value. - JavaBeans compliance: Integrates with visual editors, serialization frameworks, and other beans-aware tooling.
Step-by-Step Implementation Guide
Follow these steps to refactor the Weather class from the iluwatar/java-design-patterns repository to use PropertyChangeSupport while preserving backward compatibility with the existing WeatherObserver interface.
Add PropertyChangeSupport to the Subject
In Weather.java, declare a PropertyChangeSupport field initialized with the subject instance as the source:
import java.beans.PropertyChangeSupport;
import java.beans.PropertyChangeListener;
public class Weather {
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
// existing fields...
}
Expose Listener Registration Methods
Delegate add and remove operations to the support object. These methods follow the JavaBeans convention:
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
Fire Property Change Events
Modify the timePasses() method to fire events whenever the weather state changes. Capture the old value before updating the state:
public void timePasses() {
var enumValues = WeatherType.values();
var oldWeather = this.currentWeather;
this.currentWeather = enumValues[(this.currentWeather.ordinal() + 1) % enumValues.length];
// Notify legacy observers
notifyObservers();
// Fire PropertyChangeEvent with property name, old value, and new value
pcs.firePropertyChange("weather", oldWeather, this.currentWeather);
}
Implement PropertyChangeListener in Observers
Create a new observer class that implements java.beans.PropertyChangeListener instead of the custom WeatherObserver interface:
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class WeatherChangeLogger implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.printf("Property '%s' changed from %s to %s%n",
evt.getPropertyName(),
evt.getOldValue(),
evt.getNewValue());
}
}
Complete Integration Example
The following client code demonstrates registering both legacy WeatherObserver instances and new PropertyChangeListener implementations simultaneously:
public class WeatherMonitoringSystem {
public static void main(String[] args) {
Weather weather = new Weather();
// Legacy observer
weather.addObserver(new Orcs());
weather.addObserver(new Hobbits());
// Modern PropertyChangeListener
weather.addPropertyChangeListener(new WeatherChangeLogger());
// Trigger state changes
weather.timePasses();
weather.timePasses();
}
}
Comparing Custom Observer vs. PropertyChangeSupport
| Feature | Custom Implementation (Weather.java) |
PropertyChangeSupport Approach |
|---|---|---|
| Listener Management | Manual ArrayList<WeatherObserver> with add/remove methods |
Built-in synchronized listener list |
| Event Payload | Custom WeatherType passed directly |
PropertyChangeEvent with property name, old value, new value |
| Thread Safety | Requires manual synchronization | Thread-safe listener addition/removal |
| JavaBeans Compliance | Non-standard | Standard JavaBeans event pattern |
| Refactoring Effort | Baseline | Minimal—wrap existing notify logic |
Summary
- PropertyChangeSupport eliminates boilerplate listener list management while adding rich metadata to change notifications.
- Integrate the support class by adding it as a field in your subject, delegating registration methods, and firing events in state-change methods.
- The
iluwatar/java-design-patternsrepository'sWeatherclass can be extended to support both the legacyWeatherObserverinterface and the standardPropertyChangeListenerfor maximum flexibility. - This approach provides thread-safe operations and aligns with JavaBeans conventions, making your observer implementation compatible with frameworks that rely on standard event patterns.
Frequently Asked Questions
What is the difference between PropertyChangeSupport and the custom Observer interface in the java-design-patterns repository?
The custom WeatherObserver interface in the repository defines a specific contract with an update(WeatherType) method, requiring the subject to maintain a list of observers manually. PropertyChangeSupport is a JDK utility class that manages listener registration internally, fires PropertyChangeEvent objects containing property names and old/new values, and provides thread-safe operations without custom list management code.
Can I use PropertyChangeSupport while keeping the existing WeatherObserver implementations?
Yes, you can maintain backward compatibility by keeping the existing List<WeatherObserver> and notifyObservers() logic while adding PropertyChangeSupport for new functionality. Call pcs.firePropertyChange() immediately after notifying legacy observers in your state-change methods. This allows gradual migration where legacy observers receive direct updates while new components consume standard PropertyChangeEvent objects.
Is PropertyChangeSupport thread-safe for adding and removing listeners?
Yes, PropertyChangeSupport provides thread-safe operations for adding and removing listeners through synchronized internal data structures. However, the actual notification of listeners (firing events) is not synchronized by default to prevent deadlocks. If you require atomic notification across multiple property changes or need to ensure observers see consistent state, you should synchronize the firing logic externally or use PropertyChangeSupport's constructor that accepts a source object and ensure proper synchronization in your subject class.
When should I choose PropertyChangeSupport over RxJava or other reactive libraries?
Choose PropertyChangeSupport when building JavaBeans-compliant components, integrating with legacy Swing/JavaFX property binding, or when you need a lightweight, JDK-only solution without external dependencies. It is ideal for simple property change notifications within a single JVM. Choose RxJava or Project Reactor when you need complex stream operations (filtering, mapping, combining), backpressure handling, cross-thread scheduling, or integration with reactive web frameworks. PropertyChangeSupport is synchronous and single-purpose, while reactive libraries provide composable asynchronous event streams.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →