DEV Community

Cover image for Java HTTP Clients - 5 formas de chamar uma API REST em Java
Archimedes Fagundes Junior
Archimedes Fagundes Junior

Posted on • Edited on

Java HTTP Clients - 5 formas de chamar uma API REST em Java

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:

[
  {
    "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
Enter fullscreen mode Exit fullscreen mode

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:

<dependencies>  
    <dependency>        
        <groupId>com.fasterxml.jackson.core</groupId>  
        <artifactId>jackson-databind</artifactId>  
        <version>2.14.2</version>  
    </dependency>
</dependencies>
Enter fullscreen mode Exit fullscreen mode

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

dependencies {
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2'
}
Enter fullscreen mode Exit fullscreen mode

Em seguida vamos criar um record (para quem estiver usando o JDK 14+, se você estiver usando uma versão anterior vai ter que criar um POJO, como faziam os nossos ancestrais):

// 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) {}  
}
Enter fullscreen mode Exit fullscreen mode

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:

// 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);  
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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);  
    }  
}
Enter fullscreen mode Exit fullscreen mode

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:

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);  
    }  
}
Enter fullscreen mode Exit fullscreen mode

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:

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);  
    }  
}
Enter fullscreen mode Exit fullscreen mode

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:

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

System.out.println("Response json: " + response.body());
Enter fullscreen mode Exit fullscreen mode

Agora vamos ver como criar um novo usuário:

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);  
    }  
}
Enter fullscreen mode Exit fullscreen mode

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:

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();  
}
Enter fullscreen mode Exit fullscreen mode

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:

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)));  
}
Enter fullscreen mode Exit fullscreen mode

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

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)));  
}
Enter fullscreen mode Exit fullscreen mode

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:

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)));  
}
Enter fullscreen mode Exit fullscreen mode

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:

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)));  
}
Enter fullscreen mode Exit fullscreen mode

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

Vamos ver como criar um novo usuário:

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)));  
}
Enter fullscreen mode Exit fullscreen mode

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.

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:

@SpringBootApplication  
@EnableFeignClients
public class HttpClientFeignApplication {  

   public static void main(String[] args) {  
      SpringApplication.run(HttpClientFeignApplication.class, args);  
   }  

}
Enter fullscreen mode Exit fullscreen mode

Repare naquele @EnableFeignClients lá em cima.

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

// 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);  

}
Enter fullscreen mode Exit fullscreen mode

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:

@Component  
public class HttpClientFeign {  

    private final UserClient userClient;  

    public HttpClientFeign(UserClient userClient) {  
        this.userClient = userClient;  
    }

    // ... other methods listed below
Enter fullscreen mode Exit fullscreen mode

Retornando uma simples lista de usuários:

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);  
}
Enter fullscreen mode Exit fullscreen mode

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

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)));  
}
Enter fullscreen mode Exit fullscreen mode

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

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)));  
}
Enter fullscreen mode Exit fullscreen mode

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.

Até a próxima 🚀

Referências

Top comments (0)