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 PropertyChangeEvent objects 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-patterns repository's Weather class can be extended to support both the legacy WeatherObserver interface and the standard PropertyChangeListener for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →