A production-ready Java SDK for the OpenWeatherMap API with intelligent caching, automatic data refresh, and comprehensive weather data models.
- 🌡️ Comprehensive Weather Data - Temperature, wind, visibility, sun times, and conditions
- 🗄️ Smart Caching - LRU cache with TTL (10 minutes, max 10 cities)
- ⚡ Dual Operation Modes
- ON_DEMAND - Fetch only when requested (minimal API usage)
- POLLING - Background updates for zero-latency responses
- 🔒 Thread-Safe - Safe concurrent access across multiple threads
- 🏗️ Clean Architecture - Domain-driven design with immutable value objects
- ✅ Production-Ready - 95%+ line coverage, 90%+ branch coverage
- 📦 Minimal Dependencies - Only Jackson (JSON) and SLF4J (logging)
- Requirements
- Installation
- Quick Start
- API Reference
- Configuration
- Error Handling
- Architecture
- Testing
- Best Practices
- Troubleshooting
- Contributing
- License
- Java: 17 or higher
- Maven: 3.6.3 or higher
- OpenWeatherMap API Key: Get one for free
<dependency>
<groupId>com.weather</groupId>
<artifactId>weather-sdk</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>implementation 'com.weather:weather-sdk:1.0.0-SNAPSHOT'git clone https://github.com/sultanrasulov/weather-sdk.git
cd weather-sdk
mvn clean installimport com.weather.sdk.core.WeatherSDK;
import com.weather.sdk.core.WeatherSDKFactory;
import com.weather.sdk.domain.model.Weather;
public class WeatherApp {
public static void main(String[] args) {
WeatherSDK sdk = WeatherSDKFactory.create("your-api-key");
try {
Weather weather = sdk.getWeather("London");
System.out.println("City: " + weather.cityName());
System.out.println("Temperature: " + weather.temperature().tempCelsius() + "°C");
System.out.println("Feels like: " + weather.temperature().feelsLikeCelsius() + "°C");
System.out.println("Condition: " + weather.condition().description());
System.out.println("Wind: " + weather.wind().speed() + " m/s");
} catch (WeatherSDKException e) {
System.err.println("Error: " + e.getMessage());
} finally {
sdk.shutdown();
}
}
}import com.weather.sdk.core.OperationMode;
public class WeatherMonitor {
public static void main(String[] args) {
WeatherSDK sdk = WeatherSDKFactory.create("your-api-key", OperationMode.POLLING);
try {
// Initial fetch - populates cache
Weather london = sdk.getWeather("London");
Weather paris = sdk.getWeather("Paris");
// Background polling keeps cache fresh automatically
Thread.sleep(5000);
// Zero-latency response from updated cache
Weather updated = sdk.getWeather("London");
} catch (Exception e) {
e.printStackTrace();
} finally {
sdk.shutdown(); // Critical: stops background polling
}
}
}Singleton factory for SDK instance management.
// Create with default mode (ON_DEMAND)
WeatherSDK sdk = WeatherSDKFactory.create("api-key");
// Create with POLLING mode
WeatherSDK sdk = WeatherSDKFactory.create("api-key", OperationMode.POLLING);
// Retrieve existing instance
WeatherSDK sdk = WeatherSDKFactory.getInstance("api-key");
// Destroy instances
WeatherSDKFactory.destroy("api-key");
WeatherSDKFactory.destroyAll();Main SDK interface for weather operations.
// Fetch weather data
Weather weather = sdk.getWeather("London");
// Cache operations
boolean cached = sdk.isCached("London");
boolean fresh = sdk.isFresh("London");
Set<String> cities = sdk.getCachedCities();
sdk.clearCache();
// Lifecycle
sdk.shutdown();
boolean isShutdown = sdk.isShutdown();Rich domain model with value objects:
Weather weather = sdk.getWeather("London");
// Weather condition
weather.condition().main(); // "Clouds"
weather.condition().description(); // "scattered clouds"
// Temperature (Kelvin, Celsius, Fahrenheit)
weather.temperature().temp(); // Kelvin
weather.temperature().tempCelsius();
weather.temperature().tempFahrenheit();
weather.temperature().feelsLike();
weather.temperature().isFreezing();
weather.temperature().hasSignificantWindChill();
// Wind (with Beaufort scale)
weather.wind().speed(); // m/s
weather.wind().direction(); // degrees (0-360)
weather.wind().strength(); // WindStrength enum
weather.wind().strength().isGale();
// Visibility
weather.visibility().meters();
weather.visibility().kilometers();
weather.visibility().miles();
// Sun times
weather.sunTimes().sunrise(); // Instant
weather.sunTimes().sunset();
weather.sunTimes().daylightDuration(); // Duration
weather.sunTimes().isDaytime(Instant.now());
// Location & time
weather.cityName();
weather.timezone();
weather.datetime(); // UTC
weather.localDatetime(); // Local timeDefault: 10 cities, 10 minutes TTL, LRU eviction
import com.weather.sdk.cache.WeatherCache;
import java.time.Duration;
WeatherCache cache = new WeatherCache(
20, // 20 cities
Duration.ofMinutes(5) // 5 minutes TTL
);Default: 10 minutes
import com.weather.sdk.polling.PollingService;
PollingService polling = new PollingService(
cache,
client,
Duration.ofMinutes(5) // Poll every 5 minutes
);Comprehensive error handling with WeatherSDKException:
try {
Weather weather = sdk.getWeather("InvalidCity");
} catch (WeatherSDKException e) {
Throwable cause = e.getCause();
if (cause instanceof OpenWeatherApiException) {
// Handle API errors (401, 404, 429, 5xx)
System.err.println("API Error: " + cause.getMessage());
}
}Common Error Codes:
| Code | Description | Solution |
|---|---|---|
| 401 | Invalid API key | Verify OpenWeatherMap API key |
| 404 | City not found | Check city name spelling |
| 429 | Rate limit exceeded | Wait or upgrade API plan |
| 5xx | Server error | Retry with exponential backoff |
# Unit tests only
mvn test
# All tests (unit + integration)
mvn verify -Pci
# With coverage report
mvn clean verify
# Report: target/jacoco-report/index.html# Check formatting
mvn spotless:check
# Apply formatting
mvn spotless:apply
# Full quality check
mvn clean verify -Pci- Line Coverage: 95%
- Branch Coverage: 90%
- Class Coverage: 90%
weather-sdk/
├── src/main/java/com/weather/sdk/
│ ├── cache/ # LRU cache with TTL
│ ├── core/ # SDK core (Factory, WeatherSDK)
│ ├── domain/model/ # Domain models
│ ├── infrastructure/ # API client, DTOs, mappers
│ └── polling/ # Background polling service
└── src/test/java/ # Comprehensive test suite
- Factory Pattern -
WeatherSDKFactoryfor instance management - Singleton Pattern - One SDK instance per API key
- Value Object Pattern - Immutable domain models
- Repository Pattern -
WeatherCachefor data storage - Strategy Pattern -
OperationMode(ON_DEMAND vs POLLING)
- Immutable Value Objects - Thread-safe, predictable
- Rich Domain Model - Business logic encapsulation
- Explicit Resource Management -
shutdown()required - Case-Insensitive City Names - Normalized cache keys
- UTC-First Timestamps - UTC storage, local conversion on demand
WeatherSDK sdk = WeatherSDKFactory.create("api-key");
try {
// Use SDK
} finally {
sdk.shutdown();
}- ON_DEMAND: Infrequent queries, minimal API usage
- POLLING: Real-time monitoring, zero-latency requirements
try {
Weather weather = sdk.getWeather(city);
} catch (WeatherSDKException e) {
logger.error("Failed to fetch weather", e);
// Show fallback UI or retry
}// Good - reuse existing
WeatherSDK sdk = WeatherSDKFactory.getInstance("api-key");
// Bad - duplicate instance (throws exception)
WeatherSDK duplicate = WeatherSDKFactory.create("api-key");Solution: Verify at OpenWeatherMap API Keys
Solution: Try variations
sdk.getWeather("London");
sdk.getWeather("London,UK");
sdk.getWeather("London,GB");Solution: Check status
System.out.println("Cached: " + sdk.isCached("London"));
System.out.println("Fresh: " + sdk.isFresh("London"));
System.out.println("Cities: " + sdk.getCachedCities());Solution: Always call shutdown()
WeatherSDK sdk = WeatherSDKFactory.create("api-key", OperationMode.POLLING);
try {
// Use SDK
} finally {
sdk.shutdown(); // Stops background thread
}Contributions welcome! Follow these steps:
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Follow Google Java Format
- Maintain 95%+ coverage
- Commit changes (
git commit -m 'Add feature') - Push to branch (
git push origin feature/amazing-feature) - Open Pull Request
mvn spotless:apply
mvn clean verifyMIT License - see LICENSE file
- OpenWeatherMap - Weather API provider
- Jackson - JSON processing
- WireMock - HTTP mocking
Made with ☕ by Soltan