A high-performance REST API built with Java and Spring Boot for retrieving real-time weather data. This project integrates with the OpenWeatherMap API, implementing a resilient architecture optimized with Redis distributed caching and comprehensive unit test coverage.
Developed by: CauΓ£ Silva
Email: caua.sndias@gmail.com
GitHub: https://github.com/CauaSND/WeatherAPI
The Weather API provides a robust backend service for querying current weather information by city and state. The system implements clean architecture patterns with strategy-based dependency injection, enabling flexible infrastructure swapping without affecting business logic. This project demonstrates production-grade Java development practices including efficient caching, proper exception handling, and comprehensive API security.
- Real-time Weather Data: Retrieve current temperature, humidity, pressure, and "feels like" metrics via OpenWeatherMap integration
- Efficient Caching: Distributed Redis cache with intelligent TTL strategies (15 minutes for weather, 30 days for geolocation)
- State-aware City Matching: Automatic Unicode normalization to disambiguate cities with identical names across different Brazilian states
- Clean Architecture: Strict separation of concerns with interface-based adapters for seamless infrastructure replacement
- Comprehensive Error Handling: Custom exception hierarchy with global error handler providing structured error responses
- Automated Testing: JUnit 5 + Mockito test suite covering adapter layer and critical business logic
| Component | Technology |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 3.x (Spring Web) |
| Cache | Redis 7+ with Jedis Client |
| Testing | JUnit 5, Mockito |
| Serialization | Gson |
| Build Tool | Maven 3.x |
| Code Utilities | Project Lombok |
| Thymeleaf (RenderizaΓ§Γ£o do Front-end) | |
| Docker (ContainerizaΓ§Γ£o) |
The codebase follows hexagonal architecture principles to ensure infrastructure agnosticism:
com.api.WeatherAPI/
βββ config/ # Bean configuration & infrastructure setup
βββ controllers/ # HTTP endpoints (routing only)
βββ services/ # Business logic with cache-first pattern
βββ adapters/ # Port implementations
β βββ WeatherGateway # Interface for external API calls
β βββ OpenWeatherAdapter # OpenWeatherMap API integration
β βββ WeatherCache # Interface for caching abstraction
β βββ RedisCache # Redis persistence implementation
βββ dtos/ # Data transfer objects (Lombok @Builder)
βββ expection/ # Custom exception hierarchy
Design Principle: Services depend only on interfaces (WeatherGateway, WeatherCache), never on concrete
implementations. This allows swapping Redis for in-memory cache without modifying business logic.
GET /current/{state}/{city}Path Parameters:
state(string, required): Brazilian state name (normalized format, e.g.,sao-paulo,rio-de-janeiro)city(string, required): City name
Response:
{
"temp": 28.5,
"feels_like": 30.2,
"humidity": 65,
"pressure": 1013
}Status Codes:
200 OK: Weather data retrieved successfully404 Not Found: State/city combination not found500 Internal Server Error: OpenWeatherMap API failure or Redis connection issue
GET /location/cities/{city}Path Parameters:
city(string, required): City name to search
Response:
[
{
"name": "SΓ£o Paulo",
"state": "SΓ£o Paulo",
"lat": -23.5505,
"lon": -46.6333
}
]Status Codes:
200 OK: City list retrieved successfully500 Internal Server Error: OpenWeatherMap API unavailable
Configure the following environment variables before running:
weatherkey # OpenWeatherMap API key (required)
redisURL # Redis host (default: localhost)
redisPORT # Redis port (default: 6379)
redisPassword # Redis authentication passwordThis is a Multi-stage Build in Docker, The entire instructure (API Spring Boot + Cache Redis) runs isolated within the same Docker network , make a safe comunication, fast and in a simulation enviroment
You do not need to have Java, Maven or Redis in your machine, only Dcker can compile and execute the whole application
(If you prefer to use mock(false) data, you can skip steps 1 and 3).
- open OpenWeather log-in with a free acount.
- Go to API key section and get your API key
git clone [https://github.com/CauaSND/WeatherAPI.git](https://github.com/CauaSND/WeatherAPI.git)
cd WeatherAPIThe project has a file called .env.example. Do a copy of it and renome to .env and change only the weatherkey:
cp .env.example .envweatherkey=your-key
Security note: Put '.env' in '.gitignore' before committing. The final '.env' file you generate is ignored by Git to ensure your private keys are never pushed to the public repository.
With the .env file filled in the root, run the command below in the terminal to build the images and start all services on the same network:
docker compose up --buildOnce Spring Boot has successfully booted in the terminal, open your browser and access the front-end screen in Thymeleaf: π http://localhost:8080
- Framework: JUnit 5 with MockitoExtension
- Pattern: Given-When-Then structure with @BeforeEach fixture setup
- Coverage: Focus on adapter layer and state-matching logic
- Mocking: RestTemplate stubs for OpenWeatherMap API responses
Example test:
@ExtendWith (MockitoExtension.class)
class OpenWeatherAdapterTest {
@Mock
RestTemplate restTemplate;
@InjectMocks
OpenWeatherAdapter adapter;
@Test
void shouldMatchCityByNormalizedState () {
// Given: Multiple results for city "Osasco"
// When: Filter by state "sao-paulo"
// Then: Return coordinates for SΓ£o Paulo entry
}
}| Resource | TTL | Key Format |
|---|---|---|
| Weather Data | 15 minutes | {cityName} |
| City Geolocation | 30 days | {cityName}Cities |
The cache-first pattern ensures minimal external API calls and optimal response times:
REQUEST
β Cache HIT: Return cached data
β Cache MISS: Query OpenWeatherMap β Cache result β Return
The system disambiguates cities with identical names across Brazilian states using Unicode-aware normalization:
// Input: state = "SΓ£o Paulo"
// Process:
// 1. Decompose accents (NFD): "SaΛo Paulo"
// 2. Remove combining marks: "Sao Paulo"
// 3. Convert to lowercase: "sao paulo"
// 4. Replace spaces with hyphens: "sao-paulo"
// Output: "sao-paulo"Example: City "Osasco" exists in both SΓ£o Paulo and ParanΓ‘ states. The API returns results for both; state parameter selects the correct one.
Custom exceptions with HTTP status mapping:
-
LocationNotFoundExceptionβ HTTP 404- Thrown when state/city combination not found
-
WeatherApiExceptionβ HTTP 500- Thrown when OpenWeatherMap API fails or returns null
All exceptions are caught by @RestControllerAdvice global handler for consistent error responses.
-
src/main/java/com/api/WeatherAPI/β Source codecontrollers/β REST endpoint handlersservices/β Business logic layeradapters/β External integration portsconfig/β Spring beans and infrastructure configurationdtos/β Data transfer objectsexpection/β Custom exception classes
-
src/test/java/β Unit tests
For feature requests or bug reports, please open an issue on the GitHub repository.
This project is provided as-is for educational and development purposes.
Last Updated: June 2026
Java Version: 21
Spring Boot Version: 3.x