Building a weather data aggregator with Spring Boot

A weather data aggregator collects forecasts or current conditions from one or more locations, normalises the responses, and presents them through a clean application programming interface. Spring Boot is well suited to this job because it provides structured configuration, dependency injection, REST support, validation, and production-ready monitoring.

OpenWeatherMap supplies current conditions, forecast data, geographic coordinates, wind measurements, pressure, humidity, and weather descriptions. A Java service can call the API for several Australian cities, combine the results, and return a consistent JSON response to a website, mobile app, or dashboard.

This pattern is useful for applications serving people in Sydney, Melbourne, Brisbane, Perth, or regional areas where weather data may need to be displayed beside transport, tourism, agriculture, or outdoor event information. It also gives developers a practical example of integrating a third-party REST API with Spring Boot.

The implementation below uses Java records, RestClient, configuration properties, and a small service layer. The design keeps OpenWeatherMap details inside the backend, so frontend clients do not need to know the provider’s URL structure or API key.

Define the aggregation boundary

Start by deciding which data the application actually needs. A simple first version might accept a comma-separated list of city names and return temperature, feels-like temperature, humidity, wind speed, and a short condition such as “light rain”.

OpenWeatherMap’s current weather endpoint follows this pattern: https://api.openweathermap.org/data/2.5/weather?q={city}&appid={key}&units=metric. Using metric units gives Celsius temperatures and metres per second for wind, which is suitable for Australian users. You can later add the five-day forecast or geocoding endpoints.

A response model keeps provider-specific JSON away from controllers:

public record WeatherSummary(
    String city,
    double temperature,
    double feelsLike,
    int humidity,
    double windSpeed,
    String description
) {}

Create the Spring Boot foundation

Create a Spring Boot project with Spring Web, validation, and configuration processor dependencies. A typical package structure separates controllers, services, clients, configuration, and data transfer objects. This makes it easier to replace OpenWeatherMap or add another data source later.

Configure the provider URL and secret outside the Java source code. In application.yml, use an environment variable so the key is not committed to Git:

weather:
  base-url: https://api.openweathermap.org/data/2.5
  api-key: ${OPENWEATHER_API_KEY}

A typed configuration class is cleaner than scattering property lookups throughout the application:

@ConfigurationProperties(prefix = "weather")
public record WeatherProperties(String baseUrl, String apiKey) {}

Register it with @EnableConfigurationProperties(WeatherProperties.class) or use @ConfigurationPropertiesScan on the main application class.

Build the OpenWeatherMap client

Spring’s RestClient provides a concise synchronous HTTP client for this use case. The client should build the request, add query parameters, and map the external response into an internal model. Avoid exposing the raw provider response directly because its field names and structure are outside your control.

@Service
public class WeatherClient {
    private final RestClient http;
    private final WeatherProperties properties;

    public WeatherClient(RestClient.Builder builder, WeatherProperties properties) {
        this.http = builder.baseUrl(properties.baseUrl()).build();
        this.properties = properties;
    }

    public WeatherSummary current(String city) {
        OpenWeatherResponse response = http.get()
            .uri(uri -> uri.path("/weather")
                .queryParam("q", city)
                .queryParam("appid", properties.apiKey())
                .queryParam("units", "metric")
                .build())
            .retrieve()
            .body(OpenWeatherResponse.class);

        return map(city, response);
    }
}

In production, add an error handler for HTTP 401, 404, 429, and 5xx responses. A missing city should become a useful client error, while a provider outage should be logged and represented by a controlled application response.

Combine several locations efficiently

The aggregator service can split a request such as Sydney,Melbourne,Brisbane, trim each name, and call the client for every location. For a small number of cities, sequential calls are easy to understand and adequate. If latency becomes important, use CompletableFuture with a bounded executor rather than creating an unlimited number of threads.

Australian city names can be ambiguous. “Newcastle” may refer to New South Wales or another location, and location names can produce surprising matches. A more dependable design accepts latitude and longitude, or resolves names through OpenWeatherMap’s geocoding endpoint before retrieving conditions.

Useful safeguards include:

A cache is especially valuable when many visitors request the same city during a hot afternoon in Brisbane or before a major sporting event. It reduces rate-limit pressure while keeping displayed conditions reasonably current.

Expose a clean REST endpoint

A controller can provide an endpoint such as GET /api/weather?cities=Sydney,Perth. The controller should validate input and delegate aggregation to the service rather than containing HTTP client logic.

@RestController
@RequestMapping("/api/weather")
public class WeatherController {
    private final WeatherService service;

    @GetMapping
    public List<WeatherSummary> current(@RequestParam String cities) {
        return service.currentFor(cities);
    }
}

Return a stable application response with the request timestamp, requested locations, and any per-city errors. This is friendlier for a React frontend, a mobile app, or a WordPress integration than returning a provider-specific error document.

Add integration tests using WireMock or MockWebServer. Stub successful weather responses, invalid API keys, rate limits, malformed JSON, and unavailable upstream services. These tests verify your mapping without making real calls to OpenWeatherMap.

Handle Australian operating conditions

Australian weather applications often need clarity rather than excessive precision. Display Celsius, kilometres per hour for user-facing wind speed, local timestamps, and a clear time-zone policy. A forecast for Perth should not be labelled with Sydney time, especially when a dashboard serves users across multiple states.

Commercial use also requires attention to provider attribution, plan limits, privacy, and availability. Review the current OpenWeatherMap terms and pricing before launching a public service. A production deployment should include:

For people checking the weather on the way to the beach, a “rain expected” indicator may be more useful than a long raw description. For farmers near regional Victoria or tourism operators in Queensland, forecast windows and historical storage may justify adding a database through Spring Data JPA.

Compare endpoint choices

Choose the OpenWeatherMap endpoint according to the product requirement rather than adding every available field. Current weather is inexpensive to understand and fast to display, while forecast data is more suitable for travel planning or event scheduling.

Requirement Suitable endpoint Main advantage Design consideration
Current temperature and conditions Current weather Simple, quick response Data changes frequently
Several days of planning One Call or forecast Forecast detail and alerts Review plan access and quota
City-name resolution Geocoding Coordinates reduce ambiguity Cache resolved locations
Historical analysis Historical weather services Supports trends and reports Storage and licensing need review

A sensible first release combines the current weather endpoint with geocoding, a short cache, validation, and clear error handling. Once the service is stable, Spring Boot makes it straightforward to add scheduled refreshes, persistence, authentication, or a frontend dashboard without redesigning the core integration.