Low Level Design

Building a Scalable Weather Station

A Practical Look at Pub/Sub Architecture in Java

November 26, 2025

A Practical Look at Pub/Sub Architecture in Java Real-time systems are everywhere—live cricket scores, stock tickers, Uber ride updates, and even weather dashboards. But have you ever wondered how these systems instantly notify multiple views when something changes? In this blog, we design a Weather Station System using the Observer Design Pattern, applying clean architecture, maintainability, and extensibility.

Problem Statement#

Build a weather monitoring system where:

  • Multiple clients (mobile, web, desktop, IoT) can subscribe for weather updates.
  • Each client should receive new data only for the cities they subscribed to.
  • When weather changes, the system should instantly notify all listeners.
  • Support dynamic add/remove subscribers at runtime.

This is a classic Publish–Subscribe (Pub-Sub) model.

Why Observer Pattern?#

Observer pattern fits perfectly because: ✔ One-to-many relationship between Weather Station (Subject) & Listeners (Observers) ✔ Observers get notified automatically when data updates ✔ Loose coupling — Weather Station doesn't need to know how listeners behave ✔ Easy scalability — Add new listener types without modifying the subject

High-Level Architecture#

          ┌───────────────────────────────┐
          │         WeatherManager         │
          │ (Publisher / Subject)          │
          ├───────────────────────────────┤
          │ city → Set<WeatherListener>    │
          │ weatherMap: city → WeatherData │
          └───────────────┬───────────────┘
                          Notify()
            ┌─────────────┼─────────────┐
            │             │             │
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ MobileListener   │ │ WebListener      │ │ DesktopListener │  (easily add new)
└─────────────────┘ └─────────────────┘ └─────────────────┘

Core Components#

Data#

WeatherData — Immutable snapshot of temperature, humidity, and wind speed captured at a point in time.

WeatherEvent — Pairs a city name with its WeatherData. This is the message passed to every notified listener, keeping city and data co-located so listeners don't have to look anything up.

Contracts#

WeatherListener — The Observer interface. A single method update(WeatherEvent) is all a concrete listener needs to implement. Adding a new platform (SMS, TV, IoT) means writing a new class that implements this interface — nothing else changes.

WeatherSubject — The Subject contract that WeatherManager fulfills: subscribe, unSubscribe, and notifyListeners, all scoped per city so listeners only receive updates for the cities they care about.

Listener Implementations#

MobileWeatherListener and WebWeatherListener are concrete observers. Each formats and outputs the update for its own platform. The pattern makes it trivial to add more — just implement WeatherListener.

WeatherManager#

The heart of the system. Maintains two maps:

  • city → Set<WeatherListener> — subscriber registry
  • city → WeatherData — current weather cache

updateWeather() persists new data and immediately calls notifyListeners(), which fans the event out to every subscriber for that city.

Why This Design Works Well#

PropertyHow it's achieved
DecoupledWeatherManager holds references to WeatherListener interfaces, never to concrete classes
ScalableNew listener types require zero changes to WeatherManager
Per-city granularitySubscribers opt into only the cities they need
TestableEvery class can be tested in isolation; mock WeatherListener in unit tests

Complete Implementation#

All seven files that make up the system — browse each tab to see how they fit together:

java
public class WeatherData {
    private int temperature;
    private int humidity;
    private int windSpeed;

    public WeatherData(int temp, int humidity, int windSpeed) {
        this.temperature = temp;
        this.humidity = humidity;
        this.windSpeed = windSpeed;
    }

    public int getTemperature() { return temperature; }
    public int getHumidity()    { return humidity; }
    public int getWindSpeed()   { return windSpeed; }

    @Override
    public String toString() {
        return "Temp: " + temperature + ", Humidity: " + humidity + ", Wind: " + windSpeed;
    }
}