Explorando o RestTemplate no Spring

O RestTemplate é uma classe do Spring Framework que facilita a comunicação com APIs REST. Ele encapsula a complexidade envolvida nas requisições HTTP, permitindo que o desenvolvedor envie e receba dados de forma simples e intuitiva. Além disso, realiza automaticamente a conversão entre objetos Java e o corpo das requisições e respostas, tornando o processo muito mais prático no dia a dia.
Neste artigo, vamos utilizar uma API de exemplo que gerencia dados de clientes e expõe operações básicas de CRUD. Através dela, veremos como realizar requisições usando os quatro principais métodos HTTP — GET, POST, PUT e DELETE — e também como configurar cabeçalhos (headers) personalizados nas chamadas com o RestTemplate.
A entidade Cliente utilizada nos exemplos deste artigo contará com os seguintes campos:
id(inteiro): identificador único do clientenome(string): nome completoemail(string): endereço de e-mailtelefone(string): número de telefone para contatocpf(string): número do CPFnascimento(string): data de nascimento no formato aaaa-mm-dd
Para acompanhar os exemplos deste artigo, baixe o aplicativo da API de clientes, descompacte o arquivo e execute o comando abaixo no terminal:
java -jar cliente-api.jar
Com a aplicação rodando, você poderá visualizar e testar todos os endpoints disponíveis acessando a interface do Swagger no seguinte endereço: http://localhost:8081/swagger-ui/index.html . Lembrando que ele roda por padrão na portar 8081, mas pode ser alterado via application.properties.
Realizando uma requisição GET com RestTemplate
Antes de fazermos a requisição GET, vamos criar uma classe chamada ClienteDTO, que servirá como representação da entidade Cliente utilizada pela API. Essa classe será usada para mapear automaticamente os dados recebidos na resposta da requisição. Note que o campo nascimento está representado como um LocalDate, o que permite um tratamento mais adequado de datas em Java:
public class ClienteDTO {
private Integer id;
private String nome;
private String email;
private String telefone;
private String cpf;
private LocalDate nascimento;
private String endereco;
// Gets e Sets ...
}
Agora, vamos criar um bean de configuração para o RestTemplate. Isso nos permitirá injetá-lo facilmente em outras classes e, se necessário, adicionar configurações personalizadas no futuro, como interceptadores, timeouts ou manipuladores de erros.
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
Para manter uma boa organização do código, vamos criar uma classe de serviço responsável por encapsular as chamadas HTTP realizadas com o RestTemplate. Essa abordagem ajuda a centralizar a lógica de consumo da API em um único lugar, facilitando a manutenção e testes.
@Service
public class ClienteApiService {
private final RestTemplate restTemplate;
private ObjectMapper objectMapper;
private static final String URL_BASE = "http://localhost:8081";
public ClienteApiSerivce(RestTemplate restTemplate, ObjectMapper objectMapper) {
this.restTemplate = restTemplate;
this.objectMapper = objectMapper;
}
public List<ClienteDTO> listarClientes(){
String url = ClienteApiService.URL_BASE + "/clientes";
ClienteDTO[] clientes = restTemplate.getForObject(url, ClienteDTO[].class);
return Arrays.asList(clientes);
}
}
Como vimos no exemplo anterior, o RestTemplate e o ObjectMapper foram injetados via construtor — o ObjectMapper será utilizado em situações futuras. No método listarClientes, definimos a URL do endpoint concatenando a URL base da API com o recurso desejado (/clientes). Em seguida, utilizamos o método getForObject, que realiza uma requisição HTTP do tipo GET, enviando a URL como primeiro parâmetro e esperando, como resposta, um objeto do tipo informado no segundo parâmetro — no nosso caso, um array de ClienteDTO.
O método getForObject é simples e direto, sendo ideal para requisições que não exigem tratamento de cabeçalhos, códigos de status HTTP ou respostas complexas. No entanto, ele possui algumas limitações: não oferece controle sobre a resposta completa e não lida bem com tipos genéricos, como List<ClienteDTO>. Por isso, é necessário retornar um array (ClienteDTO[]) e, em seguida, convertê-lo manualmente para uma List usando Arrays.asList().
Quando for necessário acessar informações adicionais da resposta, como o código de status HTTP ou cabeçalhos, podemos utilizar o método getForEntity. Ele funciona de forma semelhante ao getForObject, porém retorna um objeto do tipo ResponseEntity, que fornece métodos para acessar tanto o corpo da resposta quanto os headers e o status code.
public List<ClienteDTO> listarClientes(){
String url = ClienteApiService.URL_BASE + "/clientes";
ResponseEntity<ClienteDTO[]> response = restTemplate.getForEntity(url, ClienteDTO[].class);
if(response.getStatusCode() == HttpStatus.OK){
String contentType = response.getHeaders().get("Content-Type").getFirst();
System.out.println(contentType);
ClienteDTO[] clientes = response.getBody();
return Arrays.asList(clientes);
}
throw new RuntimeException("Não foi possível realizar a requisição");
}
Como vimos no exemplo anterior, na linha 5 acessamos o código de status HTTP utilizando o método getStatusCode(). Já na linha 6, obtemos o valor do cabeçalho Content-Type por meio de getHeaders().get("Content-Type").getFirst(). Por fim, na linha 9, recuperamos o corpo da resposta com o método getBody(), que retorna o array de ClienteDTO recebido da API.
Realizando uma Requisição POST
Para enviar dados através de uma requisição POST, podemos utilizar o método postForObject(). Ele recebe três parâmetros: a URL do endpoint, o objeto que será enviado no corpo da requisição (request body) e o tipo da resposta esperada, que será convertido automaticamente para o tipo especificado.
public ClienteDTO adicionarCliente(ClienteDTO cliente){
String url = ClienteApiSerivce.URL_BASE + "/clientes";
try {
ClienteDTO clienteResponse = restTemplate.postForObject(url, cliente, ClienteDTO.class);
return clienteResponse;
} catch (HttpClientErrorException.BadRequest e) {
String json = e.getResponseBodyAsString();
System.out.println(json);
}
return null;
}
No exemplo acima, capturamos a exceção HttpClientErrorException.BadRequest, que é lançada pelo método postForObject() quando o código de resposta HTTP for 400 (Bad Request). Na nossa API, esse status é retornado quando há falhas de validação em algum campo do corpo da requisição (request body).
Para melhorar o tratamento desses erros, vamos criar uma exception personalizada que irá encapsular as informações retornadas pela API e, em seguida, lançá-la com os detalhes relevantes da resposta.
public class ErroValidacaoException extends RuntimeException {
private String error;
private LocalDateTime timestamp;
private Integer status;
private ErroCampo[] messages;
// Gets e Sets ....
public static class ErroCampo {
private String field;
private String message;
// Gets e Sets ....
}
}
Com a exception personalizada criada, podemos agora capturar a resposta de erro da requisição em formato JSON e convertê-la para um objeto da nossa classe de exceção. Isso é possível a partir do objeto HttpClientErrorException.BadRequest, lançado pelo método postForObject quando ocorre um erro com status 400. Essa exceção possui o método getResponseBodyAsString(), que retorna o corpo da resposta como uma String. Utilizando o ObjectMapper (injetado anteriormente no service), podemos desserializar essa string JSON diretamente para a nossa classe de exceção personalizada e, em seguida, lançá-la no fluxo da aplicação.
public ClienteDTO adicionarCliente(ClienteDTO cliente) {
String url = ClienteApiSerivce.URL_BASE + "/clientes";
try {
ClienteDTO clienteResponse = restTemplate.postForObject(url, cliente, ClienteDTO.class);
return clienteResponse;
} catch (HttpClientErrorException.BadRequest e) {
try {
String json = e.getResponseBodyAsString();
ErroValidacaoException validationException = objectMapper.readValue(json, ErroValidacaoException.class);
throw validationException;
} catch (JsonProcessingException e1) {
e1.printStackTrace();
}
}
return null;
}
Além do método postForObject(), também podemos utilizar o método postForEntity(), que funciona de forma semelhante ao getForEntity() já visto.
Também existe o método postForLocation(), que realiza uma requisição POST de forma semelhante aos métodos anteriores, mas em vez de retornar o corpo da resposta ou um ResponseEntity, ele retorna um objeto do tipo URI. Esse URI representa o endereço do novo recurso criado no servidor, geralmente informado pelo cabeçalho Location da resposta HTTP.
public String adicionarCliente(ClienteDTO cliente) {
String url = ClienteApiSerivce.URL_BASE + "/clientes";
try {
URI uri = restTemplate.postForLocation(url, cliente);
return uri.toString();
} catch (HttpClientErrorException.BadRequest e) {
try {
String json = e.getResponseBodyAsString();
ErroValidacaoException validationException = objectMapper.readValue(json, ErroValidacaoException.class);
throw validationException;
} catch (JsonProcessingException e1) {
e1.printStackTrace();
}
}
return null;
}
Realizando uma requisição DELETE
Para realizar uma requisição DELETE, utilizamos o método delete, fornecendo a URL do endpoint. Essa URL pode conter placeholders no formato {variavel}, que são substituídos dinamicamente ao serem passados como argumentos variáveis (varargs) após o parâmetro da URL no método delete.
public void deletar(Integer id){
String url = ClienteApiSerivce.URL_BASE + "/clientes/{id}";
try {
restTemplate.delete(url, id);
} catch (HttpClientErrorException.NotFound e) {
System.out.println("Cliente não encontrado");
}
}
Como em nossa API o endpoint retorna o status code 404 caso o cliente a ser deletado não exista, o RestTemplate lança a exceção HttpClientErrorException.NotFound. No exemplo anterior, essa exceção é capturada para tratarmos adequadamente a situação, como por exemplo informando que o cliente não foi encontrado.
Realizando uma Requisição PUT
Para realizar uma requisição PUT, utilizamos o método put(), que recebe como parâmetros a URL do endpoint, o objeto que será enviado no corpo da requisição (request body), e opcionalmente os valores de placeholders presentes na URL, caso existam.
public void atualizar(ClienteDTO cliente){
String url = ClienteApiSerivce.URL_BASE + "/clientes/{id}";
try {
restTemplate.put(url, cliente, cliente.getId());
} catch (HttpClientErrorException.NotFound e) {
System.out.println("Cliente não encontrado");
} catch (HttpClientErrorException.BadRequest e) {
try {
String json = e.getResponseBodyAsString();
ErroValidacaoException validationException = objectMapper.readValue(json, ErroValidacaoException.class);
throw validationException;
} catch (JsonProcessingException e1) {
e1.printStackTrace();
}
}
}
No exemplo anterior, como o endpoint pode retornar um erro 404 Not Found quando o cliente não é encontrado, ou um erro de validação com o status 400 Bad Request, ambos os cenários são tratados separadamente. Utilizamos blocos catch distintos para capturar e tratar cada exceção de forma apropriada, seguindo o mesmo padrão adotado nas requisições POST e DELETE.
Realizando uma Requisição Passando um Header
Quando precisamos enviar headers personalizados em uma requisição, os métodos mais simples como getForObject, postForObject ou put podem não ser suficientes. Para esses casos, utilizamos o método exchange, que é mais flexível. Ele recebe como parâmetros: a URL do endpoint, o método HTTP (especificado por uma constante de HttpMethod), um objeto HttpEntity (que encapsula os dados da requisição, como os headers e o corpo) e o tipo da classe de retorno. O método exchange retorna um objeto ResponseEntity, que fornece acesso ao corpo da resposta, status HTTP e headers.
public List<ClienteDTO> listarComHeaderAuthorization(){
try {
String url = ClienteApiSerivce.URL_BASE + "/clientes/auth";
// Geração do token
Base64.Encoder encoder = Base64.getEncoder();
String userPassword = "user:password";
String token = encoder.encodeToString(userPassword.getBytes());
// Criação do Header
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Basic " + token);
// Adicionado o header ao request
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
ResponseEntity<ClienteDTO[]> response = restTemplate.exchange(
url,
HttpMethod.GET,
requestEntity,
ClienteDTO[].class
);
return Arrays.asList( response.getBody() );
} catch (HttpClientErrorException.Forbidden e) {
System.out.println("Você não tem permssão");
}
return null;
}
No exemplo anterior, adicionamos um header de autenticação Basic à requisição. Primeiro, geramos o token codificando a combinação de usuário e senha (user:password) em Base64, na linha 9. Em seguida, criamos um objeto HttpHeaders (linha 12) e adicionamos ao header Authorization o valor "Basic " concatenado com o token gerado. Na linha 16, criamos um HttpEntity encapsulando os headers, o que nos permite enviar essa configuração junto à requisição. Por fim, utilizamos o método exchange (linha 18), passando como parâmetros a URL do endpoint, o método HTTP (HttpMethod.GET), o objeto HttpEntity com os headers, e o tipo esperado da resposta (ClienteDTO[]).
Se estivéssemos realizando uma requisição POST, poderíamos incluir o corpo da requisição (request body) diretamente no construtor do HttpEntity, juntamente com os headers. Por exemplo: new HttpEntity<>(objectRequest, headers). Dessa forma, o objeto objectRequest seria serializado como o corpo da requisição e enviado junto com a requisição.
Essa abordagem é adequada quando precisamos adicionar um header específico em apenas uma das várias requisições feitas para uma API. No entanto, se todas (ou a maioria) das requisições exigirem um header fixo, como um token de autenticação, uma alternativa mais eficiente é configurar esse header diretamente na criação do objeto RestTemplate. Por exemplo:
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
// Geração do token
Base64.Encoder encoder = Base64.getEncoder();
String userPassword = "user:password";
String token = encoder.encodeToString(userPassword.getBytes());
return builder
.defaultHeader("Authorization", "Basic " + token)
.build();
}
}
Como estamos utilizando autenticação Basic, o RestTemplateBuilder já fornece um método específico que recebe o usuário e a senha, codificando-os automaticamente no formato adequado para o header Authorization.
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.basicAuthentication("user", "password")
.build();
}
O RestTemplateBuilder oferece diversos métodos utilitários que facilitam a configuração do RestTemplate, permitindo definir conversores de mensagens, timeouts, errorHandlers e outros recursos importantes de forma fluente e centralizada.
Um exemplo mais prático, consultando um CEP com RestTemplate
Vamos agora a um exemplo mais prático. É bastante comum que sistemas que envolvem o cadastro de endereços solicitem o CEP e preencham automaticamente os demais campos, como cidade, rua e bairro. Isso geralmente é feito consultando uma API de CEP. Existem várias disponíveis, inclusive gratuitas. Vamos usar como exemplo a API do ViaCep, que é bastante simples de utilizar.
O endpoint dela é o seguinte:
https://viacep.com.br/ws/{cep}/json/
E o JSON de resposta tem a seguinte estrutura:
{
"cep": "96090-620",
"logradouro": "Rua Santa Cruz do Sul",
"complemento": "",
"unidade": "",
"bairro": "Laranjal",
"localidade": "Pelotas",
"uf": "RS",
"estado": "Rio Grande do Sul",
"regiao": "Sul",
"ibge": "4314407",
"gia": "",
"ddd": "53",
"siafi": "8791"
}
Primeiro, vamos criar uma classe DTO que represente essa resposta e nos permita convertê-la automaticamente
public class CepResponseDTO {
private String cep;
private String logradouro;
private String complemento;
private String unidade;
private String bairro;
private String localidade;
private String uf;
private String estado;
private String regiao;
private String ibge;
private String gia;
private String ddd;
private String siafi;
// Gets e Sets ...
}
Em seguida, vamos implementar nosso serviço responsável por realizar a requisição à API:
public class CepApiService {
private final RestTemplate restTemplate;
public CepApiService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public CepResponseDTO cep(String cep){
try{
String url = "https://viacep.com.br/ws/{cep}/json/";
return restTemplate.getForObject(url, CepResponseDTO.class, cep);
}catch(HttpClientErrorException.NotFound e){
throw new RuntimeException("Cep não encontrado");
}
}
}
A chamada é bastante simples: utilizamos o método getForObject, informando a URL do endpoint com o placeholder, o tipo da classe que representará a resposta (CepResponseDTO.class) e o valor que substituirá o placeholder na URL. Com isso, já podemos utilizar esse serviço normalmente em nosso código.
CepResponseDTO cepResponse = cepApiService.cep("96090-620");
System.out.println("CEP: " + cepResponse.getCep());
System.out.println("UF: " + cepResponse.getUf());
System.out.println("Localidade: " + cepResponse.getLocalidade());
System.out.println("Bairro: " + cepResponse.getBairro());
System.out.println("Logradouro: " + cepResponse.getLogradouro());
Encerramos aqui esta introdução ao RestTemplate, uma forma simples e direta de realizar requisições a APIs REST utilizando o Spring Framework. Embora o RestTemplate ainda seja amplamente utilizado — especialmente em projetos legados —, o Spring recomenda, para novos projetos, a adoção do WebClient (introduzido no Spring 5), que oferece uma abordagem mais moderna, reativa e flexível para consumo de serviços HTTP. T++
