Java HttpClient
Выполняйте HTTP-запросы в Java 11+ с java.net.http.HttpClient: синхронный и асинхронный режимы, поддержка HTTP/2.
java.net.http.HttpClient, стандартизированный в Java 11, является современным HTTP API и предпочтительным выбором для нового кода. Он заменяет многословный HttpURLConnection неизменяемым дизайном на основе строителя: HTTP/2 по умолчанию, нативные синхронные и асинхронные вызовы, а также подключаемая обработка тел запросов и ответов. Три типа выполняют основную работу — HttpClient, HttpRequest и HttpResponse.
В этой главе рассматривается создание и повторное использование клиента, построение запросов с телами и заголовками, синхронный и асинхронный режимы отправки, то, как BodyHandler преобразует ответ в нужный тип, а также типичные ошибки начинающих. Если вы новичок в работе с сетью на Java, начните с введения в сетевое программирование; об асинхронном строительном блоке, используемом здесь, читайте в CompletableFuture.
Три основных типа
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();Один экземпляр HttpClient является потокобезопасным и многократно используемым — создайте его один раз и используйте во всём приложении; не создавайте новый клиент для каждого запроса. Через него вы отправляете объекты HttpRequest и получаете объекты HttpResponse.
Построение запроса
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();Метод HTTP-глагола (GET(), POST(...), PUT(...), DELETE()) выбирается в строителе. BodyPublisher предоставляет тело запроса — ofString, ofByteArray, ofFile или noBody(). Запросы неизменяемы после построения и могут быть повторно использованы.
Отправка: синхронная и асинхронная
// 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());BodyHandler определяет, как материализуется тело ответа: ofString(), ofByteArray(), ofFile(path), ofLines() (это Stream<String>) или discarding(). sendAsync возвращает CompletableFuture, поэтому вы можете выстраивать цепочки .thenApply, .thenAccept и .exceptionally без блокировки потока.
Чтение ответа
HttpResponse<T> — это простой типизированный объект-значение. Наиболее часто используемые методы:
statusCode()— HTTP-статус в видеint(например,200,404). МетодаisSuccessful()нет; проверяйте код самостоятельно.body()— тело, уже преобразованное вTпереданнымBodyHandler.headers()— объектHttpHeaders. ИспользуйтеfirstValue("Content-Type")(возвращаетOptional<String>) илиallValues("Set-Cookie")для повторяющихся заголовков.uri()— итоговый URI, который может отличаться от URI запроса после перенаправления.
Имена заголовков сопоставляются без учёта регистра, поэтому firstValue("content-type") и firstValue("Content-Type") возвращают одно и то же значение.
Практический пример: синхронный GET, синхронный POST и асинхронный запрос
Эта программа создаёт петлевую конечную точку, которая сообщает полученный HTTP-метод, а затем обращается к ней тремя способами через один общий HttpClient: синхронный GET, синхронный POST с телом и асинхронный GET через CompletableFuture.
Что можно извлечь из этого примера:
- Один
HttpClientобслужил все три запроса. Клиент неизменяем и потокобезопасен, поэтому правильный подход — создать один раз и использовать везде; создание нового клиента на каждый вызов расточительно для пулов соединений и сессий HTTP/2. Обратите внимание, чтоdisconnect()нигде не вызывается — клиент управляет соединениями самостоятельно. - HTTP-глагол задаётся в строителе:
.GET()для чтения и.POST(BodyPublishers.ofString("payload"))для записи. Сервер вернул использованный метод (handled GET,handled POST), подтверждая, что publisher и передал тело, и установил глагол. HttpResponse— это типизированный объект с методамиstatusCode()иbody(). Поскольку был переданBodyHandlers.ofString(), тело вернулось уже декодированным какString— замените его наofByteArray,ofFileилиofLines, и тот же вызов вернёт байты, сохранённый файл или поток строк.- Асинхронный вызов вернул
CompletableFutureи выстроил цепочку через.thenApplyбез блокировки потока вплоть доfuture.get(). Это ключевое архитектурное преимущество передHttpURLConnection: параллелизм встроен, поэтому сотни одновременных запросов не требуют сотен заблокированных потоков. - Весь поток — клиент, запрос, синхронная и асинхронная отправки, типизированные ответы — обошёлся без ручной работы с потоками и без ловушки потока ошибок. По сравнению с
HttpURLConnectionHttpClientкороче, безопаснее и функциональнее, поэтому он является выбором по умолчанию на Java 11+.
Типичные ошибки
- Коды 4xx и 5xx не являются исключениями. В отличие от некоторых библиотек,
HttpClientнормально возвращает ответ для любого полученного статуса; исключениеIOExceptionвыбрасывается только при транспортных сбоях (отказ соединения, таймаут, DNS). Всегда проверяйтеstatusCode()— тело ответа с ошибкой по-прежнему доступно для чтения. - Создайте один клиент и используйте его совместно.
HttpClientнеизменяем, потокобезопасен и владеет собственным пулом соединений. Создание нового клиента на каждый запрос уничтожает повторное использование соединений и сессии HTTP/2. Методаclose()нет вплоть до Java 21 (а в Java 21+ он реализуетAutoCloseable, однако долгоживущий общий клиент редко нуждается в закрытии). connectTimeoutиtimeout— это разные вещи.HttpClient.connectTimeout(...)ограничивает время установки TCP-соединения;HttpRequest.timeout(...)ограничивает весь цикл запроса/ответа. Запрос, достигший таймаута, завершает future исключительно сHttpTimeoutException.GETне может содержать тело. ВызовGET()сBodyPublisherне соответствует принципам API — используйтеPOST,PUTили обобщённыйmethod(name, publisher)для глаголов, отправляющих данные.URI.createтребует абсолютного, корректно сформированного URI. Пробелы и другие небезопасные символы не кодируются автоматически; закодируйте параметры запроса перед построением URI.