W3docs

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.

java— editable, runs on the server

Что можно извлечь из этого примера:

  • Один 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: параллелизм встроен, поэтому сотни одновременных запросов не требуют сотен заблокированных потоков.
  • Весь поток — клиент, запрос, синхронная и асинхронная отправки, типизированные ответы — обошёлся без ручной работы с потоками и без ловушки потока ошибок. По сравнению с HttpURLConnection HttpClient короче, безопаснее и функциональнее, поэтому он является выбором по умолчанию на 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.

Практика

Практика
В высоконагруженном сервисе разработчик пишет 'HttpClient.newHttpClient()' внутри метода, обрабатывающего каждый входящий запрос, создавая тем самым новый клиент на каждый вызов. Ревьюеры указывают на проблему. В чём она?
В высоконагруженном сервисе разработчик пишет 'HttpClient.newHttpClient()' внутри метода, обрабатывающего каждый входящий запрос, создавая тем самым новый клиент на каждый вызов. Ревьюеры указывают на проблему. В чём она?
Was this page helpful?