W3docs

Java HttpClient

Faça requisições HTTP em Java moderno com java.net.http.HttpClient, incluindo suporte a chamadas síncronas, assíncronas e HTTP/2.

java.net.http.HttpClient, padronizado no Java 11, é a API HTTP moderna e a escolha certa para código novo. Ele substitui o verboso HttpURLConnection por um design imutável baseado em builder: HTTP/2 por padrão, chamadas síncronas e assíncronas nativas, e tratamento plugável de corpos de requisição e resposta. Três tipos fazem o trabalho — HttpClient, HttpRequest e HttpResponse.

Este capítulo aborda a construção e o reuso de um cliente, a construção de requisições com corpos e cabeçalhos, os modos de envio síncrono e assíncrono, como os BodyHandlers convertem uma resposta no tipo desejado, e os erros comuns que pegam usuários de primeira viagem. Se você é novo em rede em Java, comece pela introdução a redes; para o bloco de construção assíncrono usado aqui, consulte CompletableFuture.

Os três tipos

HttpClient client = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_2)       // HTTP/2, falling back to 1.1
        .connectTimeout(Duration.ofSeconds(10))
        .followRedirects(HttpClient.Redirect.NORMAL)
        .build();

Um único HttpClient é thread-safe e reutilizável — construa um e compartilhe-o por toda a aplicação; não crie um por requisição. A partir dele, você envia objetos HttpRequest e recebe de volta objetos HttpResponse.

Construindo uma requisição

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://example.com/api"))
        .header("Accept", "application/json")
        .timeout(Duration.ofSeconds(5))
        .POST(HttpRequest.BodyPublishers.ofString("{\"x\":1}"))
        .build();

O método do verbo (GET(), POST(...), PUT(...), DELETE()) é escolhido no builder. Um BodyPublisher fornece o corpo da requisição — ofString, ofByteArray, ofFile ou noBody(). As requisições são imutáveis uma vez construídas e podem ser reutilizadas.

Envio: síncrono e assíncrono

// Blocking
HttpResponse<String> resp =
        client.send(request, HttpResponse.BodyHandlers.ofString());

// Non-blocking — returns a CompletableFuture
CompletableFuture<HttpResponse<String>> future =
        client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

Um BodyHandler decide como o corpo da resposta é materializado: ofString(), ofByteArray(), ofFile(path), ofLines() (um Stream<String>), ou discarding(). sendAsync retorna um CompletableFuture, então você encadeia .thenApply, .thenAccept e .exceptionally sem bloquear uma thread.

Lendo a resposta

HttpResponse<T> é um objeto de valor tipado simples. Os métodos mais utilizados:

  • statusCode() — o status HTTP como int (ex.: 200, 404). Não existe isSuccessful(); verifique o código você mesmo.
  • body() — o corpo, já convertido para T pelo BodyHandler passado.
  • headers() — um HttpHeaders. Use firstValue("Content-Type") (retorna um Optional<String>) ou allValues("Set-Cookie") para cabeçalhos repetidos.
  • uri() — o URI final, que pode diferir do URI da requisição após um redirecionamento.

Os nomes de cabeçalhos são comparados sem diferenciação de maiúsculas e minúsculas, portanto firstValue("content-type") e firstValue("Content-Type") retornam o mesmo valor.

Exemplo completo: GET síncrono, POST síncrono e assíncrono

Este programa serve um endpoint de loopback que reporta o método HTTP recebido, e então o aciona de três formas com um único HttpClient compartilhado: um GET síncrono, um POST síncrono com corpo, e um GET assíncrono por meio de um CompletableFuture.

java— editable, runs on the server

O que extrair da execução:

  • Um HttpClient atendeu todas as três requisições. O cliente é imutável e thread-safe, então o padrão correto é construir uma vez e compartilhar em toda parte; criar um cliente por chamada desperdiça pools de conexão e sessões HTTP/2. Observe que não há disconnect() em lugar algum — o cliente gerencia as conexões por você.
  • O verbo da requisição ficou no builder: .GET() para leitura e .POST(BodyPublishers.ofString("payload")) para escrita. O servidor ecoou de volta o método que recebeu (handled GET, handled POST), confirmando que o publisher tanto transportou o corpo quanto definiu o verbo.
  • HttpResponse é um objeto tipado com statusCode() e body(). Como um BodyHandlers.ofString() foi passado, o corpo voltou já decodificado como String — substitua por ofByteArray, ofFile ou ofLines e a mesma chamada retorna bytes, um arquivo salvo, ou um stream de linhas.
  • A chamada assíncrona retornou um CompletableFuture e foi composta com .thenApply sem bloquear uma thread até future.get(). Essa é a vantagem estrutural sobre HttpURLConnection: concorrência é embutida, portanto centenas de requisições em andamento não precisam significar centenas de threads bloqueadas.
  • Todo o fluxo — cliente, requisição, envios síncrono e assíncrono, respostas tipadas — não usou streams manuais nem armadilha de error-stream. Comparado com HttpURLConnection, HttpClient é mais curto, mais seguro e mais capaz, razão pela qual é a escolha padrão no Java 11+.

Erros comuns

  • Um 4xx ou 5xx não é uma exceção. Ao contrário de algumas bibliotecas, HttpClient retorna a resposta normalmente para qualquer status recebido; somente falhas de transporte (conexão recusada, timeout, DNS) lançam IOException. Sempre inspecione statusCode() — o corpo de uma resposta de erro ainda é legível.
  • Construa um cliente, compartilhe-o. HttpClient é imutável e thread-safe e possui seu próprio pool de conexões. Criar um por requisição descarta o reuso de conexões e sessões HTTP/2. Não há close() a chamar antes do Java 21 (e no Java 21+ ele é AutoCloseable, mas um cliente compartilhado de longa duração raramente precisa ser fechado).
  • connectTimeout e timeout são diferentes. HttpClient.connectTimeout(...) limita quanto tempo o estabelecimento da conexão TCP pode levar; HttpRequest.timeout(...) limita toda a requisição/resposta. Uma requisição que expira completa o future de forma excepcional com um HttpTimeoutException.
  • Um GET não pode carregar um corpo. Chamar GET() com um BodyPublisher não é como a API funciona — use POST, PUT ou o genérico method(name, publisher) para verbos que enviam dados.
  • URI.create deve receber um URI absoluto e bem formado. Espaços e outros caracteres inseguros não são codificados para você; codifique os parâmetros de consulta antes de construir o URI.

Prática

Prática
Em um serviço de alto tráfego, um desenvolvedor escreve 'HttpClient.newHttpClient()' dentro do método que trata cada requisição recebida, criando um novo cliente por chamada. Os revisores sinalizam o problema. Por quê?
Em um serviço de alto tráfego, um desenvolvedor escreve 'HttpClient.newHttpClient()' dentro do método que trata cada requisição recebida, criando um novo cliente por chamada. Os revisores sinalizam o problema. Por quê?
Was this page helpful?