API REST em Java com Spring Boot que consome uma API externa de meteorologia e disponibiliza as informações climáticas de Belo Horizonte - MG em JSON.
Atividade 01 - API REST de Clima com Spring Boot.
Open-Meteo. Escolhemos ela porque é gratuita e não exige API Key nem cadastro, então nenhuma credencial precisa ser publicada no repositório.
- Java 21
- Spring Boot 4.1.1
- Maven (via wrapper, não precisa instalar)
Dependências do pom.xml:
| Dependência | Uso |
|---|---|
spring-boot-starter-webmvc |
Spring MVC, Tomcat embarcado e conversão JSON |
spring-boot-starter-validation |
validação dos parâmetros dos endpoints |
spring-boot-configuration-processor |
metadados das propriedades clima.* |
spring-boot-starter-webmvc-test |
JUnit 5, Mockito, AssertJ e MockMvc (testes) |
spring-boot-starter-validation-test |
validação nos testes |
A chamada HTTP para a API externa usa o RestClient do Spring, que já vem no
spring-web.
Pré-requisitos: JDK 21 ou superior e conexão com a internet.
git clone https://github.com/<usuario>/<repositorio>.git
cd <repositorio>Windows:
.\mvnw.cmd spring-boot:runLinux / macOS:
./mvnw spring-boot:runDepois que aparecer Started ClimaApiApplication, acesse:
http://localhost:8080/clima
Para gerar e rodar o jar:
./mvnw clean package
java -jar target/clima-api-0.0.1-SNAPSHOT.jarPara trocar a porta:
./mvnw spring-boot:run "-Dspring-boot.run.arguments=--server.port=8081"A Open-Meteo não exige API Key, então a aplicação roda sem configuração adicional.
Mesmo assim a chave não fica no código. Em application.properties:
clima.api-key=${CLIMA_API_KEY:}Se a API externa for trocada por uma que exija autenticação (WeatherAPI, OpenWeather, Tomorrow.io), basta definir a variável de ambiente:
# Windows
$env:CLIMA_API_KEY = "sua-chave-aqui"# Linux / macOS
export CLIMA_API_KEY="sua-chave-aqui"Ou por linha de comando:
./mvnw spring-boot:run "-Dspring-boot.run.arguments=--clima.api-key=sua-chave-aqui"Quando a propriedade está preenchida, o OpenMeteoClient envia o parâmetro
apikey na requisição. Quando está vazia, o parâmetro é omitido.
O .gitignore bloqueia .env, *.env, application-local.properties e
application-secret.properties.
| Propriedade | Padrão |
|---|---|
clima.api-url |
https://api.open-meteo.com/v1/forecast |
clima.geocoding-url |
https://geocoding-api.open-meteo.com/v1/search |
clima.api-key |
vazio (lido de CLIMA_API_KEY) |
clima.timezone |
America/Sao_Paulo |
clima.connect-timeout |
5s |
clima.read-timeout |
10s |
clima.cidade-padrao.* |
Belo Horizonte / MG / -19.9167, -43.9345 |
Base: http://localhost:8080
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /clima |
clima atual de Belo Horizonte |
| GET | /clima/belo-horizonte |
mesmo resultado do anterior |
| GET | /clima/previsao?dias={n} |
previsão dos próximos dias em BH |
| GET | /clima/cidade/{cidade} |
clima atual de outra cidade |
| GET | /clima/cidade/{cidade}/previsao?dias={n} |
previsão de outra cidade |
O parâmetro dias é opcional, vale 7 por padrão e aceita valores de 1 a 16.
curl http://localhost:8080/climaResposta (200):
{
"localizacao": {
"cidade": "Belo Horizonte",
"estado": "Minas Gerais",
"pais": "Brasil",
"latitude": -19.9297,
"longitude": -43.966034,
"altitude": 843.0,
"fusoHorario": "America/Sao_Paulo"
},
"consultadoEm": "2026-08-24T19:44:32.117-03:00",
"observadoEm": "2026-08-24T19:30",
"condicao": "Garoa moderada",
"descricao": "Garoa moderada em Belo Horizonte, com 19,1 C e umidade de 81%.",
"codigoTempo": 53,
"temperatura": {
"atual": 19.1,
"sensacaoTermica": 19.7,
"maxima": 26.1,
"minima": 16.4,
"unidade": "C"
},
"umidade": 81,
"vento": {
"velocidade": 9.7,
"unidade": "km/h",
"direcaoGraus": 132,
"direcaoCardeal": "SE"
},
"precipitacao": 0.2,
"pressao": 1019.6,
"periodo": "noite",
"fonte": "Open-Meteo (https://open-meteo.com)"
}curl "http://localhost:8080/clima/previsao?dias=3"Resposta (200):
{
"localizacao": {
"cidade": "Belo Horizonte",
"estado": "Minas Gerais",
"pais": "Brasil",
"latitude": -19.9297,
"longitude": -43.966034,
"altitude": 843.0,
"fusoHorario": "America/Sao_Paulo"
},
"consultadoEm": "2026-08-24T19:44:32.728-03:00",
"quantidadeDias": 3,
"previsoes": [
{
"data": "2026-08-24",
"diaSemana": "segunda-feira",
"condicao": "Garoa moderada",
"codigoTempo": 53,
"temperaturaMaxima": 26.1,
"temperaturaMinima": 16.4,
"probabilidadeChuva": 75,
"precipitacaoTotal": 1.5,
"ventoMaximo": 15.6,
"nascerDoSol": "2026-08-24T06:10",
"porDoSol": "2026-08-24T17:45"
},
{
"data": "2026-08-25",
"diaSemana": "terça-feira",
"condicao": "Nublado",
"codigoTempo": 3,
"temperaturaMaxima": 27.4,
"temperaturaMinima": 16.4,
"probabilidadeChuva": 47,
"precipitacaoTotal": 0.0,
"ventoMaximo": 11.6,
"nascerDoSol": "2026-08-25T06:09",
"porDoSol": "2026-08-25T17:45"
}
],
"fonte": "Open-Meteo (https://open-meteo.com)"
}O nome da cidade é convertido em coordenadas pelo serviço de geocoding da Open-Meteo. Acentos e espaços precisam ser codificados na URL.
curl "http://localhost:8080/clima/cidade/Curitiba"
curl "http://localhost:8080/clima/cidade/S%C3%A3o%20Paulo"O formato da resposta é o mesmo de GET /clima.
curl "http://localhost:8080/clima/cidade/Curitiba/previsao?dias=5"Mesmo formato de GET /clima/previsao.
Os erros passam pelo GlobalExceptionHandler e saem sempre no mesmo formato:
{
"timestamp": "2026-08-24T19:44:36.463-03:00",
"status": 404,
"erro": "Cidade nao encontrada",
"mensagem": "Nao foi possivel localizar a cidade informada: Atlantida123",
"caminho": "/clima/cidade/Atlantida123"
}| Status | Quando acontece |
|---|---|
| 400 | dias fora do intervalo 1-16 ou não numérico |
| 404 | cidade não encontrada no geocoding |
| 404 | rota inexistente |
| 502 | a API externa respondeu com erro ou com corpo ilegível |
| 502 | a API externa respondeu sem os dados meteorológicos |
| 503 | timeout, sem rede ou conexão recusada com a API externa |
| 500 | qualquer falha não prevista |
Os timeouts (clima.connect-timeout e clima.read-timeout) evitam que uma
queda da API externa deixe as requisições travadas.
./mvnw testOs testes usam mocks no lugar do cliente HTTP, então rodam sem acessar a internet.
| Classe | O que cobre |
|---|---|
ClimaServiceTest |
conversão dos dados da API externa e cenários de dados ausentes |
ClimaControllerTest |
endpoints, formato do JSON e status de erro |
CodigoTempoTest |
tradução dos códigos de condição climática |
DirecaoVentoTest |
conversão de graus para ponto cardeal |
ClimaApiApplicationTests |
carregamento do contexto |
src/main/java/com/faculdade/clima/
├── ClimaApiApplication.java
├── config/ ClimaProperties, RestClientConfig
├── controller/ ClimaController, GlobalExceptionHandler
├── service/ ClimaService
├── client/ OpenMeteoClient e os DTOs da API externa
├── dto/ objetos devolvidos pela nossa API
├── exception/ exceções próprias
└── util/ CodigoTempo, DirecaoVento
Os DTOs ficam separados em dois pacotes: client/dto tem o formato da
Open-Meteo e dto tem o formato que a nossa API devolve. Assim uma mudança na
API externa não muda o contrato dos nossos endpoints.
- consulta do clima de outras cidades pelo nome
- previsão para os próximos dias (1 a 16)
- dados organizados em objetos próprios da aplicação
- tratamento de erro separado por tipo de falha da API externa
- tradução dos códigos de condição climática para português
- direção do vento convertida para ponto cardeal
| Integrante | GitHub |
|---|---|