W3docs

Java HttpClient

Esegui richieste HTTP in Java moderno con java.net.http.HttpClient, con supporto per modalità sincrona, asincrona e HTTP/2.

java.net.http.HttpClient, standardizzato in Java 11, è l'API HTTP moderna da utilizzare nel nuovo codice. Sostituisce il verboso HttpURLConnection con un design immutabile basato su builder: HTTP/2 di default, chiamate native sia sincrone che asincrone, e gestione configurabile del corpo di richieste e risposte. Tre tipi svolgono il lavoro — HttpClient, HttpRequest e HttpResponse.

Questo capitolo tratta la costruzione e il riutilizzo di un client, la creazione di richieste con corpo e intestazioni, le modalità di invio sincrono e asincrono, come i BodyHandler trasformano una risposta nel tipo desiderato, e le insidie più comuni per i nuovi utenti. Se sei alle prime armi con il networking in Java, inizia dall'introduzione al networking; per il blocco di costruzione asincrono usato qui, consulta CompletableFuture.

I tre tipi

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 singolo HttpClient è thread-safe e riutilizzabile — costruiscine uno e condividilo in tutta l'applicazione; non crearne uno per ogni richiesta. Da esso invii oggetti HttpRequest e ricevi oggetti HttpResponse.

Costruire una richiesta

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

Il metodo del verbo (GET(), POST(...), PUT(...), DELETE()) viene scelto sul builder. Un BodyPublisher fornisce il corpo della richiesta — ofString, ofByteArray, ofFile, o noBody(). Le richieste sono immutabili una volta costruite e possono essere riutilizzate.

Invio: sincrono e asincrono

// 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 come viene materializzato il corpo della risposta: ofString(), ofByteArray(), ofFile(path), ofLines() (un Stream<String>), o discarding(). sendAsync restituisce un CompletableFuture, quindi puoi concatenare .thenApply, .thenAccept e .exceptionally senza bloccare un thread.

Leggere la risposta

HttpResponse<T> è un semplice oggetto valore tipizzato. I metodi più utilizzati:

  • statusCode() — lo stato HTTP come int (ad es. 200, 404). Non esiste isSuccessful(); controlla il codice manualmente.
  • body() — il corpo, già convertito in T dal BodyHandler passato.
  • headers() — un HttpHeaders. Usa firstValue("Content-Type") (restituisce un Optional<String>) o allValues("Set-Cookie") per intestazioni ripetute.
  • uri() — l'URI finale, che può differire dall'URI della richiesta dopo un redirect.

I nomi delle intestazioni vengono confrontati senza distinzione tra maiuscole e minuscole, quindi firstValue("content-type") e firstValue("Content-Type") restituiscono lo stesso valore.

Un esempio completo: GET sincrono, POST sincrono e asincrono

Questo programma espone un endpoint di loopback che riporta il metodo HTTP ricevuto, poi lo utilizza in tre modi con un singolo HttpClient condiviso: un GET sincrono, un POST sincrono con corpo e un GET asincrono tramite CompletableFuture.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Un solo HttpClient ha servito tutte e tre le richieste. Il client è immutabile e thread-safe, quindi il pattern corretto è costruire-una-volta-condividere-ovunque; creare un client per ogni chiamata spreca pool di connessioni e sessioni HTTP/2. Nota che non c'è nessun disconnect() — il client gestisce le connessioni per te.
  • Il verbo della richiesta era sul builder: .GET() per la lettura e .POST(BodyPublishers.ofString("payload")) per la scrittura. Il server ha rimandato il metodo ricevuto (handled GET, handled POST), confermando che il publisher ha sia trasportato il corpo che impostato il verbo.
  • HttpResponse è un oggetto tipizzato con statusCode() e body(). Poiché è stato passato un BodyHandlers.ofString(), il corpo è tornato già decodificato come String — sostituisci con ofByteArray, ofFile, o ofLines e la stessa chiamata produce byte, un file salvato o un flusso di righe.
  • La chiamata asincrona ha restituito un CompletableFuture e si è composta con .thenApply senza bloccare un thread fino a future.get(). Questo è il vantaggio strutturale rispetto a HttpURLConnection: la concorrenza è integrata, quindi centinaia di richieste in volo non richiedono centinaia di thread in attesa.
  • L'intero flusso — client, richiesta, invii sincroni e asincroni, risposte tipizzate — non ha usato stream manuali né trappole per lo stream degli errori. Rispetto a HttpURLConnection, HttpClient è più breve, più sicuro e più capace, ecco perché è la scelta predefinita su Java 11+.

Insidie comuni

  • Un 4xx o 5xx non è un'eccezione. A differenza di alcune librerie, HttpClient restituisce la risposta normalmente per qualsiasi stato ricevuto; solo i fallimenti di trasporto (connessione rifiutata, timeout, DNS) lanciano IOException. Ispeziona sempre statusCode() — il corpo di una risposta di errore è ancora leggibile.
  • Costruisci un solo client e condividilo. HttpClient è immutabile e thread-safe e possiede il suo pool di connessioni. Crearne uno per richiesta elimina il riutilizzo delle connessioni e le sessioni HTTP/2. Non c'è un close() da chiamare prima di Java 21 (e su Java 21+ è AutoCloseable, ma un client condiviso a lunga vita raramente ha bisogno di essere chiuso).
  • connectTimeout e timeout sono diversi. HttpClient.connectTimeout(...) limita il tempo per stabilire la connessione TCP; HttpRequest.timeout(...) limita l'intera richiesta/risposta. Una richiesta che va in timeout completa il future in modo eccezionale con un HttpTimeoutException.
  • Un GET non può trasportare un corpo. Chiamare GET() con un BodyPublisher non è il modo in cui funziona l'API — usa POST, PUT, o il generico method(name, publisher) per i verbi che inviano dati.
  • URI.create deve ricevere un URI assoluto e ben formato. Spazi e altri caratteri non sicuri non vengono codificati automaticamente; codifica i parametri di query prima di costruire l'URI.

Pratica

Pratica
In un servizio ad alto traffico, uno sviluppatore scrive 'HttpClient.newHttpClient()' all'interno del metodo che gestisce ogni richiesta in arrivo, creando un nuovo client per ogni chiamata. I revisori lo segnalano. Perché?
In un servizio ad alto traffico, uno sviluppatore scrive 'HttpClient.newHttpClient()' all'interno del metodo che gestisce ogni richiesta in arrivo, creando un nuovo client per ogni chiamata. I revisori lo segnalano. Perché?
Was this page helpful?