W3docs

Java HttpClient

Realiza solicitudes HTTP en Java moderno con java.net.http.HttpClient, incluyendo soporte síncrono, asíncrono y HTTP/2.

java.net.http.HttpClient, estandarizado en Java 11, es la API HTTP moderna y la que se debe utilizar en código nuevo. Reemplaza al verboso HttpURLConnection con un diseño inmutable basado en constructores: HTTP/2 por defecto, llamadas síncronas y asíncronas nativas, y manejo conectable de cuerpos de solicitud y respuesta. Tres tipos hacen el trabajo — HttpClient, HttpRequest y HttpResponse.

Este capítulo cubre la construcción y reutilización de un cliente, la construcción de solicitudes con cuerpos y encabezados, los modos de envío síncrono y asíncrono, cómo los BodyHandlers convierten una respuesta en el tipo que deseas, y los problemas que afectan a los usuarios nuevos. Si eres nuevo en el networking de Java, comienza con la introducción al networking; para el bloque de construcción asíncrono utilizado aquí, consulta CompletableFuture.

Los tres 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();

Un único HttpClient es seguro para hilos y reutilizable — construye uno y compártelo en toda la aplicación; no crees uno por solicitud. A partir de él envías objetos HttpRequest y recibes objetos HttpResponse.

Construyendo una solicitud

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

El método de verbo (GET(), POST(...), PUT(...), DELETE()) se elige en el constructor. Un BodyPublisher suministra el cuerpo de la solicitud — ofString, ofByteArray, ofFile o noBody(). Las solicitudes son inmutables una vez construidas y pueden reutilizarse.

Envío: síncrono y así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());

Un BodyHandler decide cómo se materializa el cuerpo de la respuesta: ofString(), ofByteArray(), ofFile(path), ofLines() (un Stream<String>) o discarding(). sendAsync devuelve un CompletableFuture, por lo que puedes encadenar .thenApply, .thenAccept y .exceptionally sin bloquear un hilo.

Leyendo la respuesta

HttpResponse<T> es un objeto de valor tipado simple. Los métodos más utilizados:

  • statusCode() — el estado HTTP como un int (por ejemplo, 200, 404). No existe isSuccessful(); comprueba el código tú mismo.
  • body() — el cuerpo, ya convertido a T por el BodyHandler que pasaste.
  • headers() — un HttpHeaders. Usa firstValue("Content-Type") (devuelve un Optional<String>) o allValues("Set-Cookie") para encabezados repetidos.
  • uri() — el URI final, que puede diferir del URI de la solicitud después de una redirección.

Los nombres de encabezado se comparan sin distinción entre mayúsculas y minúsculas, por lo que firstValue("content-type") y firstValue("Content-Type") devuelven el mismo valor.

Un ejemplo completo: GET síncrono, POST síncrono y asíncrono

Este programa sirve un endpoint de loopback que reporta el método HTTP que recibió, y luego lo acciona de tres maneras con un único HttpClient compartido: un GET síncrono, un POST síncrono con cuerpo, y un GET asíncrono a través de un CompletableFuture.

java— editable, runs on the server

Lo que hay que destacar de la ejecución:

  • Un único HttpClient sirvió las tres solicitudes. El cliente es inmutable y seguro para hilos, por lo que el patrón correcto es construir-una-vez-compartir-en-todas-partes; crear un cliente por llamada desperdicia grupos de conexiones y sesiones HTTP/2. Observa que no hay ningún disconnect() en ningún lugar — el cliente gestiona las conexiones por ti.
  • El verbo de la solicitud vivía en el constructor: .GET() para la lectura y .POST(BodyPublishers.ofString("payload")) para la escritura. El servidor devolvió el método que recibió (handled GET, handled POST), confirmando que el publisher tanto llevó el cuerpo como estableció el verbo.
  • HttpResponse es un objeto tipado con statusCode() y body(). Dado que se pasó un BodyHandlers.ofString(), el cuerpo regresó ya decodificado como un String — intercambia ofByteArray, ofFile u ofLines y la misma llamada produce bytes, un archivo guardado o un stream de líneas.
  • La llamada asíncrona devolvió un CompletableFuture y se compuso con .thenApply sin bloquear un hilo hasta future.get(). Esa es la ventaja estructural sobre HttpURLConnection: la concurrencia está integrada, por lo que cientos de solicitudes en vuelo no necesitan significar cientos de hilos aparcados.
  • Todo el flujo — cliente, solicitud, envíos síncrono y asíncrono, respuestas tipadas — no utilizó flujos manuales ni la trampa del flujo de error. Comparado con HttpURLConnection, HttpClient es más corto, más seguro y más capaz, razón por la cual es la opción predeterminada en Java 11+.

Problemas comunes

  • Un 4xx o 5xx no es una excepción. A diferencia de algunas bibliotecas, HttpClient devuelve la respuesta normalmente para cualquier estado que recibió; solo los fallos de transporte (conexión rechazada, tiempo de espera, DNS) lanzan IOException. Siempre inspecciona statusCode() — el cuerpo de una respuesta de error sigue siendo legible.
  • Construye un cliente, compártelo. HttpClient es inmutable y seguro para hilos y posee su grupo de conexiones. Crear uno por solicitud descarta la reutilización de conexiones y las sesiones HTTP/2. No hay ningún close() que llamar antes de Java 21 (y en Java 21+ es AutoCloseable, pero un cliente compartido de larga duración raramente necesita cerrarse).
  • connectTimeout y timeout son diferentes. HttpClient.connectTimeout(...) limita cuánto tiempo puede tardar en establecerse la conexión TCP; HttpRequest.timeout(...) limita toda la solicitud/respuesta. Una solicitud que agota el tiempo de espera completa el futuro de forma excepcional con una HttpTimeoutException.
  • Un GET no puede llevar un cuerpo. Llamar a GET() con un BodyPublisher no es como funciona la API — usa POST, PUT o el genérico method(name, publisher) para verbos que envían datos.
  • URI.create debe recibir un URI absoluto y bien formado. Los espacios y otros caracteres no seguros no se codifican por ti; codifica los parámetros de consulta antes de construir el URI.

Práctica

Práctica
En un servicio de alto tráfico, un desarrollador escribe 'HttpClient.newHttpClient()' dentro del método que maneja cada solicitud entrante, creando un cliente nuevo por llamada. Los revisores lo señalan. ¿Por qué?
En un servicio de alto tráfico, un desarrollador escribe 'HttpClient.newHttpClient()' dentro del método que maneja cada solicitud entrante, creando un cliente nuevo por llamada. Los revisores lo señalan. ¿Por qué?
Was this page helpful?