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 comeint(ad es.200,404). Non esisteisSuccessful(); controlla il codice manualmente.body()— il corpo, già convertito inTdalBodyHandlerpassato.headers()— unHttpHeaders. UsafirstValue("Content-Type")(restituisce unOptional<String>) oallValues("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.
Cosa ricavare dall'esecuzione:
- Un solo
HttpClientha 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'è nessundisconnect()— 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 constatusCode()ebody(). Poiché è stato passato unBodyHandlers.ofString(), il corpo è tornato già decodificato comeString— sostituisci conofByteArray,ofFile, oofLinese la stessa chiamata produce byte, un file salvato o un flusso di righe.- La chiamata asincrona ha restituito un
CompletableFuturee si è composta con.thenApplysenza bloccare un thread fino afuture.get(). Questo è il vantaggio strutturale rispetto aHttpURLConnection: 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,
HttpClientrestituisce la risposta normalmente per qualsiasi stato ricevuto; solo i fallimenti di trasporto (connessione rifiutata, timeout, DNS) lancianoIOException. Ispeziona semprestatusCode()— 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'è unclose()da chiamare prima di Java 21 (e su Java 21+ èAutoCloseable, ma un client condiviso a lunga vita raramente ha bisogno di essere chiuso). connectTimeoutetimeoutsono 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 unHttpTimeoutException.- Un
GETnon può trasportare un corpo. ChiamareGET()con unBodyPublishernon è il modo in cui funziona l'API — usaPOST,PUT, o il genericomethod(name, publisher)per i verbi che inviano dati. URI.createdeve 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.