Java HTTP Clients - 5 formas de chamar uma API REST em Java
Tags: java, braziliandevs, tutorial, cheatsheet
Published: 2023-04-03 12:35:13

Uma das grandes vantagens de desenvolver em Java é a quantidade maciça de bibliotecas e frameworks disponíveis para executar qualquer tarefa necessária. Isso se deve a uma comunidade muito ativa e dedicada à linguagem criada há quase 30 anos.

No entanto, uma das desvantagens é que podemos nos sentir um pouco sobrecarregados com as diferentes implementações, configurações, formas de utilização e boas práticas. Isso também se aplica às chamadas de APIs REST (ou qualquer outro tipo de requisição HTTP), pois há muitas bibliotecas para escolher: HttpURLConnection, HttpClient, RestTemplate e WebClient do Spring, e Spring Cloud OpenFeign.

O objetivo do artigo é apresentar exemplos simples de duas implementações para cada uma dessas bibliotecas - uma solicitação GET e uma POST - para criar um "cheat sheet" para consultas futuras.

> Todos os exemplos estão no repositório https://github.com/afagundes/java-http-clients

Bora lá 😎

## Pré-requisitos

First things first.

Todos os exemplos abaixo consultarão a mesma API disponível em https://jsonplaceholder.typicode.com/users. Aliás, recomendo bastante esse site para quem estiver estudando REST e criando as primeiras requisições. Tem vários endpoints disponíveis lá.

O endpoint escolhido retorna uma lista de usuários no seguinte formato:

```json
[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  },
  ... vários outros usuários
```

Nós queremos consultar a API e converter o JSON retornado para um objeto. Para isso vamos adicionar a seguinte biblioteca no nosso pom.xml:

```xml
<dependencies>  
    <dependency>        
	    <groupId>com.fasterxml.jackson.core</groupId>  
        <artifactId>jackson-databind</artifactId>  
        <version>2.14.2</version>  
    </dependency>
</dependencies>
```

Se você estiver usando Gradle, é um pouquinho menos verboso 😅:

```groovy
dependencies {
	implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2'
}
```

Em seguida vamos criar um [record](https://docs.oracle.com/en/java/javase/14/language/records.html) (para quem estiver usando o JDK 14+, se você estiver usando uma versão anterior vai ter que criar um [POJO](https://www.javatpoint.com/pojo-in-java), como faziam os nossos ancestrais):

```java
// User.java

public record User(  
        Integer id,  
        String name,  
        String username,  
        String email,  
        Address address,  
        String phone,  
        String website,  
        Company company)  
{  
    public record Address(
		    String street, 
		    String suite, 
		    String city, 
		    String zipcode, 
		    Geo geo)
	{  
        public record Geo(String lat, String lng) {}  
    }  
  
    public record Company(String name, String catchPhrase, String bs) {}  
}
```

E, por fim, para facilitar a nossa vida, vamos criar uma classe utilitária que contenha alguns métodos para converter um objeto JSON para o formato de objeto e vice-versa, entre outras funcionalidades:

```java
// ExampleUtils.java

public class ExampleUtils {  
  
    private ExampleUtils() {}  
  
    public static final String USER_API = "https://jsonplaceholder.typicode.com/users";  
    public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();  
  
    public static List<User> toList(InputStream inputStream) {  
        try {  
            return OBJECT_MAPPER.readValue(inputStream, new TypeReference<>() {});  
        }  
        catch (IOException exc) {  
            throw new UncheckedIOException(exc);  
        }  
    }  
  
    public static User toObject(InputStream inputStream) {  
        try {  
            return OBJECT_MAPPER.readValue(inputStream, User.class);  
        }  
        catch (IOException exc) {  
            throw new UncheckedIOException(exc);  
        }  
    }  
  
    public static String toJson(User user) {  
        try {  
            return OBJECT_MAPPER.writeValueAsString(user);  
        }  
        catch (JsonProcessingException exc) {  
            throw new UncheckedIOException(exc);  
        }
    }
    
	public static User buildUser() {  
	    User.Address address = new User.Address(  
	            "Rua http 200",  
	            "apto POST",  
	            "São Paulo",  
	            "00200-404",  
	            new User.Address.Geo("-257422", "25566987"));  
	  
	    User.Company company = new User.Company(  
	            "My Great Company",  
	            "We develop software!",  
	            "sofware, development, java");  
	  
	    return new User(null,  
	            "Archimedes Fagundes Junior",  
	            "archimedes.junior",  
	            "archimedes.junior@dev.com",  
	            address,  
	            "11 95523-9999",  
	            "https://my.company.com",  
	            company);  
	}
}
```

Maravilha! Sem mais enrolações, vamos para a nossa primeira implementação: A classe **HttpURLConnection**.

## Old School HttpURLConnection

A primeira implementação a ser explorada é a `HttpURLConnection`. Essa implementação é a mais antiga e está presente desde a JDK 1.1. Abaixo está um exemplo de um método que exibe uma lista de usuários usando o método GET e a classe auxiliar:

```java
public void listUsers() {  
    System.out.println("\nListing users using the old school HttpURLConnection:");  
  
    try {  
        URL url = new URL(ExampleUtils.USER_API);  
        HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();  
        httpURLConnection.setRequestMethod("GET");  
        int responseCode = httpURLConnection.getResponseCode();  
  
        System.out.println("HTTP status: " + responseCode);  
        System.out.println("Users returned in request: ");  
  
        List<User> users = ExampleUtils.toList(httpURLConnection.getInputStream());  
        users.forEach(System.out::println);  
  
        System.out.println("Headers:");  
        httpURLConnection.getHeaderFields().forEach((header, value) -> System.out.println(header + " = " + String.join(", ", value)));  
    }  
    catch (MalformedURLException e) {  
        throw new RuntimeException("You've entered an invalid URL here: " + ExampleUtils.USER_API);  
    }  
    catch (IOException e) {  
        throw new RuntimeException("Error processing request", e);  
    }  
}
```

Observe a linha que contém o trecho `List<User> users = ExampleUtils.toList(httpURLConnection.getInputStream())`.  Aqui um `InputStream`  é obtido e passado para nosso método auxiliar. Não há métodos para obter o JSON diretamente como uma string. Vamos seguir usando a abordagem do InputStream para os outros exemplos também.

Agora, vamos tentar criar um usuário usando o método POST e exibir o retorno:

```java
public void createNewUser() {  
    System.out.println("\nCreating a new user using the old school HttpURLConnection:");  
    User user = ExampleUtils.buildUser();  
  
    try {  
        URL url = new URL(ExampleUtils.USER_API);  
        HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();  
        httpURLConnection.setRequestMethod("POST");  
        httpURLConnection.setRequestProperty("Content-Type", "application/json");  
  
        String userJson = ExampleUtils.toJson(user);  
  
        httpURLConnection.setDoOutput(true);  
        try (OutputStream outputStream = httpURLConnection.getOutputStream()) {  
            outputStream.write(userJson.getBytes(StandardCharsets.UTF_8));  
            outputStream.flush();  
        }  
  
        int responseCode = httpURLConnection.getResponseCode();  
        System.out.println("HTTP status: " + responseCode);  
  
        User createdUser = ExampleUtils.toObject(httpURLConnection.getInputStream());  
        System.out.println("Created new user: " + createdUser);  
  
        System.out.println("Headers:");  
        httpURLConnection.getHeaderFields().forEach((header, value) -> System.out.println(header + " = " + String.join(", ", value)));  
    }  
    catch (MalformedURLException e) {  
        throw new RuntimeException("You've entered an invalid URL here: " + ExampleUtils.USER_API);  
    }  
    catch (IOException e) {  
        throw new RuntimeException("Error processing request", e);  
    }  
}
```

Dois pontos interesantes a serem observados:

1. Adicionamos um novo cabeçalho à requisição usando `httpURLConnection.setRequestProperty("Content-Type", "application/json");`
2. Para enviarmos algum valor no corpo da requisição, precisamos primeiro habilitar o outpt com `httpURLConnection.setDoOutput(true)`, obter uma referência ao objeto `OutputStream` e escrever o valor nele. Cansativo.

Felizmente, há uma solução mais moderna: o **HttpClient** do Java 11.

## Java 11 HttpClient

O JDK 11, lançado em Setembro de 2018, trouxe um novo cliente HTTP mais simples e fácil de utilizar. Vamos dar uma olhada em como listar os usuários do endpoint:

```java
public void listUsers() {  
    System.out.println("\nListing users using Java 11 HttpClient:");  
  
    HttpClient httpClient = HttpClient.newHttpClient();  
    HttpRequest request = HttpRequest.newBuilder(URI.create(ExampleUtils.USER_API)).GET().build();  
  
    try {  
        HttpResponse<InputStream> response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());  
  
        int statusCode = response.statusCode();  
        System.out.println("HTTP status: " + statusCode);  
  
        System.out.println("Users returned in request: ");  
        List<User> users = ExampleUtils.toList(response.body());  
        users.forEach(System.out::println);  
  
        System.out.println("Headers:");  
        response.headers().map().forEach((header, value) -> System.out.println(header + " = " + String.join(", ", value)));  
    }  
    catch (IOException | InterruptedException e) {  
        throw new RuntimeException(e);  
    }  
}
```

O exemplo está obtendo o retorno da API como um `InputStream` para que possamos continuar usando nossos métodos auxiliares, mas se quisermos pegar o JSON como uma String, podemos fazer:

```json
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

System.out.println("Response json: " + response.body());
```

Agora vamos ver como criar um novo usuário:

```json
public void createNewUser() {  
    System.out.println("\nCreating a new user using Java 11 HttpClient:");  
  
    User user = ExampleUtils.buildUser();  
    HttpRequest.BodyPublisher userPublisher = HttpRequest.BodyPublishers.ofString(ExampleUtils.toJson(user));  
  
    HttpClient httpClient = HttpClient.newHttpClient();  
    HttpRequest request = HttpRequest  
            .newBuilder(URI.create(ExampleUtils.USER_API))  
            .POST(userPublisher)  
            .setHeader("Content-Type", "application/json")  
            .build();  
  
    try {  
        HttpResponse<InputStream> response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());  
  
        int statusCode = response.statusCode();  
        System.out.println("HTTP status: " + statusCode);  
  
        User createdUser = ExampleUtils.toObject(response.body());  
        System.out.println("Created new user: " + createdUser);  
  
        System.out.println("Headers:");  
        response.headers().map().forEach((header, value) -> System.out.println(header + " = " + String.join(", ", value)));  
    }  
    catch (IOException | InterruptedException e) {  
        throw new RuntimeException(e);  
    }  
}
```

Bem mais claro e fluente que sua classe avó 😁

No exemplo acima, fizemos uma requisição POST síncrona, ou seja, nossa aplicação ficará parada aguardando o retorno da API. Caso queiramos fazer uma chamada assíncrona utilizando a classe antiga `HttpURLConnection`, precisaremos criar uma nova thread e executar nosso código nela. Felizmente, a nova classe `HttpClient` permite realizar chamadas assíncronas sem a necessidade de esforço adicional. O trecho de código a seguir apresenta um exemplo de criação de um novo usuário de forma assíncrona:

```java
public void createNewUserAsync() {  
    System.out.println("\nCreating a new user asynchronously using Java 11 HttpClient:");  
  
    User user = ExampleUtils.buildUser();  
    HttpRequest.BodyPublisher userPublisher = HttpRequest.BodyPublishers.ofString(ExampleUtils.toJson(user));  
  
    HttpClient httpClient = HttpClient.newHttpClient();  
    HttpRequest request = HttpRequest  
            .newBuilder(URI.create(ExampleUtils.USER_API))  
            .POST(userPublisher)  
            .setHeader("Content-Type", "application/json")  
            .build();  
  
    httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream())  
            .thenApply(response -> {  
                System.out.println("Http status: " + response.statusCode());  
                System.out.println("Headers:");  
                response.headers().map().forEach((header, value) -> System.out.println(header + " = " + String.join(", ", value)));  
                return response;  
            })  
            .thenApply(HttpResponse::body)  
            .thenApply(ExampleUtils::toObject)  
            .thenAccept(createdUser -> System.out.println("New user created asynchronously: " + createdUser))  
            .join();  
}
```

OK. Vimos as implementações disponíveis na API do Java. Agora vamos dar uma olhada em duas bibliotecas, uma muito conhecida, outras nem tanto. Vamos começar pela mais conhecida.

## Spring RestTemplate

Projetos criados utilizando Spring Boot web já possuem na caixa de ferramentas a classe `RestTemplate` disponível para uso. Vamos ver como pegar a lista de usuários com ela:

```java
public void listUsers() {  
    System.out.println("\nListing users using Spring RestTemplate:");  
  
    RestTemplate restTemplate = new RestTemplate();  
    ResponseEntity<User[]> response = restTemplate.getForEntity(URI.create(ExampleUtils.USER_API), User[].class);  
  
    HttpStatusCode statusCode = response.getStatusCode();  
    System.out.println("HTTP status: " + statusCode.value());  
    System.out.println("Is status code 2xx successful? " + statusCode.is2xxSuccessful());  
  
    System.out.println("Users returned in request: ");  
    User[] users = Objects.requireNonNull(response.getBody());  
    Arrays.stream(users).forEach(System.out::println);  
  
    System.out.println("Headers:");  
    response.getHeaders().forEach((header, value) -> System.out.println(header + " = " + String.join(", ", value)));  
}
```

E como criar um novo usuário na nossa API:

```java
public void createNewUser() {  
    System.out.println("\nCreating a new user using Spring RestTemplate:");  
  
    User user = ExampleUtils.buildUser();  
  
    RestTemplate restTemplate = new RestTemplate();  
    ResponseEntity<User> response = restTemplate.postForEntity(URI.create(ExampleUtils.USER_API), user, User.class);  
  
    HttpStatusCode statusCode = response.getStatusCode();  
    System.out.println("HTTP status: " + statusCode.value());  
    System.out.println("Is status code 2xx successful? " + statusCode.is2xxSuccessful());  
  
    User createdUser = response.getBody();  
    System.out.println("Created new user: " + createdUser);  
  
    System.out.println("Headers:");  
    response.getHeaders().forEach((header, value) -> System.out.println(header + " = " + String.join(", ", value)));  
}
```

Note que dessa vez não precisamos usar nossos métodos auxiliares para converter o `InputStream` ou String no objeto User. O RestTemplate já cuida dessa parte para nós.

Nos exemplos acima usamos os métodos `getForEntity()` e `postForEntity()` para interagir com a API. Também podemos usar o método mais genérico `exchange`. Vamos ver como criar um novo usuário com esse método:

```java
public void createNewUserUsingExchangeMethod() {  
    System.out.println("\nCreating a new user using Spring RestTemplate's exchange method:");  
  
    RestTemplate restTemplate = new RestTemplate();  
  
    RequestEntity<User> request = RequestEntity  
            .post(URI.create(ExampleUtils.USER_API))  
            .contentType(MediaType.APPLICATION_JSON)  
            .header(HttpHeaders.AUTHORIZATION, "Bearer XYZ1234abc")  
            .body(ExampleUtils.buildUser());  
  
    ResponseEntity<User> response = restTemplate.exchange(request, User.class);  
  
    HttpStatusCode statusCode = response.getStatusCode();  
    System.out.println("HTTP status: " + statusCode.value());  
    System.out.println("Is status code 2xx successful? " + statusCode.is2xxSuccessful());  
  
    User createdUser = response.getBody();  
    System.out.println("Created new user with exchange method: " + createdUser);  
  
    System.out.println("Headers:");  
    response.getHeaders().forEach((header, value) -> System.out.println(header + " = " + String.join(", ", value)));  
}
```

## Spring WebClient

Ainda dentro do universo do Spring Boot, mas dessa vez para os que trabalham com código reativo usando a biblioteca **Spring WebFlux**, temos a classe **WebClient**. Agora nosso código vai ficando cada vez menos verboso.

Vamos listar os usuários usando o WebClient:

```java
public void listUsers() {  
    System.out.println("\nListing users using Spring WebFlux:");  
  
    WebClient webClient = WebClient.create(ExampleUtils.USER_API);  
    ResponseEntity<List<User>> response = webClient.get().retrieve().toEntityList(User.class).block();  
  
    Objects.requireNonNull(response);  
    HttpStatusCode statusCode = response.getStatusCode();  
    System.out.println("HTTP status: " + statusCode.value());  
    System.out.println("Is status code 2xx successful? " + statusCode.is2xxSuccessful());  
  
    System.out.println("Users returned in request: ");  
    List<User> users = Objects.requireNonNull(response.getBody());  
    users.forEach(System.out::println);  
  
    System.out.println("Headers:");  
    response.getHeaders().forEach((header, value) -> System.out.println(header + " = " + String.join(", ", value)));  
}
```

Pouquíssimas linhas de configuração. Agora está ficando bom de verdade 😁.

Vamos ver como criar um novo usuário:

```java
public void createNewUser() {  
    System.out.println("\nCreating a new user using Spring WebClient:");  
  
    User user = ExampleUtils.buildUser();  
    WebClient webClient = WebClient.create(ExampleUtils.USER_API);  
    ResponseEntity<User> response = webClient.post()  
            .contentType(MediaType.APPLICATION_JSON)  
            .accept(MediaType.APPLICATION_JSON)  
            .body(Mono.just(user), User.class)  
            .retrieve()  
            .toEntity(User.class)  
            .block();  
  
    Objects.requireNonNull(response);  
    HttpStatusCode statusCode = response.getStatusCode();  
    System.out.println("HTTP status: " + statusCode.value());  
    System.out.println("Is status code 2xx successful? " + statusCode.is2xxSuccessful());  
  
    User createdUser = response.getBody();  
    System.out.println("Created new user: " + createdUser);  
  
    System.out.println("Headers:");  
    response.getHeaders().forEach((header, value) -> System.out.println(header + " = " + String.join(", ", value)));  
}
```

O WebClient é uma biblioteca reativa que trabalha com fluxos de dados, como `Flux` e `Mono`, por padrão. Para os nossos exemplos simples, usamos o método `block()` para fazer com que o cliente espere até que todos os dados da requisição sejam recebidos antes de retornar as entidades. No entanto, se você quiser se aprofundar em Spring WebFlux e entender o que são Flux e Mono, recomendo dar uma olhada na documentação oficial disponível em [https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux](https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux).

## Spring Cloud OpenFeign

Agora, vamos subir alguns degraus na escada da abstração e usar uma biblioteca chamada **OpenFeign**, que foi integrada aos módulos do **Spring Cloud**.

O conceito é extremamente simples: você criar um interface, adiciona algumas anotações e voilá, temos um cliente HTTP funcional. O Spring cuida da parte chatas.

Antes de usar o Feign client, precisamos habilitá-lo. Para isso, adicionamos uma anotação na classe principal de nossa aplicação:

```java
@SpringBootApplication  
@EnableFeignClients
public class HttpClientFeignApplication {  
  
   public static void main(String[] args) {  
      SpringApplication.run(HttpClientFeignApplication.class, args);  
   }  
  
}
```

Repare naquele `@EnableFeignClients` lá em cima.

Agora, vamos criar nossa interface responsável por se comunicar com a API de usuários:

```java
// UserClient.java

@FeignClient(name="userClient", url = ExampleUtils.USER_API)  
public interface UserClient {  
  
    @GetMapping  
    List<User> simpleListUsers();  
  
    @GetMapping  
    ResponseEntity<List<User>> listUsers();  
  
    @PostMapping  
    ResponseEntity<User> createNewUser(User user);  
  
}
```

E é exatamente isso e nada mais! Aqui criamos três métodos:

1. O método `simpleListUsers()` apenas nos retorna a lista de usuários, como se fosse uma chamada de método qualquer.
2. O método `listUsers()` nos retorna um objeto do tipo `ResponseEntity` para que possamos inspecionar os headers, status da requisição, etc.
3. O método `createNewUser()` para criar um novo usuário.

Vamos ver como usar cada um deles.

Mas antes, não esqueça de injetar o client como uma dependência da sua classe:

```java
@Component  
public class HttpClientFeign {  
  
    private final UserClient userClient;  
  
    public HttpClientFeign(UserClient userClient) {  
        this.userClient = userClient;  
    }

	// ... other methods listed below
```

Retornando uma simples lista de usuários:

```java
public void simpleListUsers() {  
    System.out.println("\nListing users using OpenFeign client:");  
    List<User> users = userClient.simpleListUsers();  
  
    System.out.println("Users returned in request: ");  
    users.forEach(System.out::println);  
}
```

Retornando a lista de usuários e informações sobre a requisição:

```java
public void listUsers() {  
    System.out.println("\nListing users using OpenFeign client:");  
    ResponseEntity<List<User>> response = userClient.listUsers();  
  
    int statusCode = response.getStatusCode().value();  
    System.out.println("HTTP status: " + statusCode);  
  
    System.out.println("Users returned in request: ");  
    List<User> users = Objects.requireNonNull(response.getBody());  
    users.forEach(System.out::println);  
  
    System.out.println("Headers:");  
    response.getHeaders().forEach((header, value) -> System.out.println(header + " = " + String.join(", ", value)));  
}
```

E, por fim, criando um novo usuário:

```java
public void createNewUser() {  
    System.out.println("\nCreating a new user using OpenFeign client:");  
  
    User user = ExampleUtils.buildUser();  
    ResponseEntity<User> response = userClient.createNewUser(user);  
  
    int statusCode = response.getStatusCode().value();  
    System.out.println("HTTP status: " + statusCode);  
  
    User createdUser = response.getBody();  
    System.out.println("Created new user: " + createdUser);  
  
    System.out.println("Headers:");  
    response.getHeaders().forEach((header, value) -> System.out.println(header + " = " + String.join(", ", value)));  
}
```

Simples e extremamente prático!

## Conclusão

Esse artigo não tem a pretensão de ser conclusivo, mas sim um guia de consulta rápida para as bibliotecas que uso em meus projetos. Há ainda muito mais recursos pra explorar dentro dessas bibliotecas. Também não mencionei outras bibliotecas interessantes, como o Http Client do Micronaut ou da Apache Commons, mas vou deixar uns links pra quem tiver interesse.

Como dito acima, todos esses exemplos, incluindo suas configurações e classes adicionais, estão lá no meu [Github](https://github.com/afagundes/java-http-clients).

Até a próxima 🚀


### Referências

- https://www.digitalocean.com/community/tutorials/java-httpurlconnection-example-java-http-request-get-post
- https://www.baeldung.com/java-9-http-client
- https://www.baeldung.com/rest-template
- https://reflectoring.io/spring-webclient/
- https://www.baeldung.com/spring-cloud-openfeign
- https://guides.micronaut.io/latest/micronaut-http-client-maven-java.html
- https://www.vogella.com/tutorials/ApacheHttpClient/article.html