Статьи

Полный курс по языку Java — от установки JDK до многопоточности и HTTP-запросов

HTTP-запросы

Страница 30 из 30 · 2026-10-25 · 8 мин чтения

HttpClient

Начиная с Java 11 в JDK есть современный HTTP-клиент java.net.http. Внешние библиотеки для простых задач не нужны. Он поддерживает HTTP/1.1, HTTP/2, асинхронные запросы и WebSocket.

Основные классы:

КлассНазначение
HttpClientклиент, переиспользуется для многих запросов
HttpRequestописание запроса
HttpResponse<T>ответ с телом типа T
BodyHandlersкак читать тело ответа
BodyPublishersкак отправлять тело запроса

GET-запрос

import java.io.IOException;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;public class GetDemo {    public static void main(String[] args) throws IOException, InterruptedException {        HttpClient client = HttpClient.newHttpClient();        HttpRequest request = HttpRequest.newBuilder()            .uri(URI.create("https://jsonplaceholder.typicode.com/todos/1"))            .GET()            .build();        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());        System.out.println(response.statusCode());        System.out.println(response.headers().firstValue("content-type").orElse("нет"));        System.out.println(response.body());    }}

Пример ответа:

200application/json; charset=utf-8{  "userId": 1,  "id": 1,  "title": "delectus aut autem",  "completed": false}

send блокирует поток до получения ответа и может бросить IOException и InterruptedException.

Настройка клиента

import java.net.http.HttpClient;import java.time.Duration;public class ClientConfig {    static final HttpClient CLIENT = HttpClient.newBuilder()        .version(HttpClient.Version.HTTP_2)        .connectTimeout(Duration.ofSeconds(5))        .followRedirects(HttpClient.Redirect.NORMAL)        .build();}

Создавайте один клиент на приложение: он управляет пулом соединений. Таймаут на ответ задается у запроса через .timeout(Duration).

POST с JSON

import java.io.IOException;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.time.Duration;public class PostDemo {    public static void main(String[] args) throws IOException, InterruptedException {        HttpClient client = HttpClient.newHttpClient();        String json = """            {              "title": "Купить хлеб",              "body": "Сегодня до 18:00",              "userId": 1            }            """;        HttpRequest request = HttpRequest.newBuilder()            .uri(URI.create("https://jsonplaceholder.typicode.com/posts"))            .timeout(Duration.ofSeconds(10))            .header("Content-Type", "application/json; charset=UTF-8")            .header("Accept", "application/json")            .POST(HttpRequest.BodyPublishers.ofString(json))            .build();        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());        System.out.println(response.statusCode());        System.out.println(response.body());    }}

Другие методы: PUT(...), DELETE(), method("PATCH", publisher).

Форма и параметры запроса

Параметры в адресе нужно кодировать:

import java.net.URI;import java.net.URLEncoder;import java.net.http.HttpRequest;import java.nio.charset.StandardCharsets;public class QueryParams {    public static void main(String[] args) {        String query = URLEncoder.encode("java курс", StandardCharsets.UTF_8);        URI uri = URI.create("https://example.com/search?q=" + query + "&page=2");        System.out.println(uri);        String form = "login=" + URLEncoder.encode("anna", StandardCharsets.UTF_8)            + "&password=" + URLEncoder.encode("p@ss word", StandardCharsets.UTF_8);        HttpRequest request = HttpRequest.newBuilder()            .uri(URI.create("https://example.com/login"))            .header("Content-Type", "application/x-www-form-urlencoded")            .POST(HttpRequest.BodyPublishers.ofString(form))            .build();        System.out.println(request.method());    }}

Авторизация и заголовки

import java.net.URI;import java.net.http.HttpRequest;import java.nio.charset.StandardCharsets;import java.util.Base64;public class AuthDemo {    public static void main(String[] args) {        HttpRequest bearer = HttpRequest.newBuilder()            .uri(URI.create("https://api.example.com/profile"))            .header("Authorization", "Bearer eyJ...токен")            .header("User-Agent", "JavaCourse/1.0")            .build();        String credentials = Base64.getEncoder()            .encodeToString("user:password".getBytes(StandardCharsets.UTF_8));        HttpRequest basic = HttpRequest.newBuilder()            .uri(URI.create("https://api.example.com/profile"))            .header("Authorization", "Basic " + credentials)            .build();        System.out.println(bearer.headers().map());        System.out.println(basic.headers().map());    }}

Не храните токены и пароли в исходном коде. Читайте их из переменных окружения: System.getenv("API_TOKEN").

Проверка статуса

Ответ с кодом 404 или 500 для HttpClient не считается исключением: проверять нужно самому.

КодСмысл
200успех
201создано
204успех, тела нет
301, 302перенаправление
400неверный запрос
401не авторизован
403доступ запрещен
404не найдено
429слишком много запросов
500ошибка сервера
import java.io.IOException;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;public class StatusCheck {    static String fetch(HttpClient client, String url) throws IOException, InterruptedException {        HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());        int status = response.statusCode();        if (status >= 200 && status < 300) {            return response.body();        }        if (status == 404) {            throw new IOException("Ресурс не найден: " + url);        }        throw new IOException("Ошибка HTTP " + status + " для " + url);    }    public static void main(String[] args) throws IOException, InterruptedException {        HttpClient client = HttpClient.newHttpClient();        System.out.println(fetch(client, "https://jsonplaceholder.typicode.com/todos/1"));        try {            fetch(client, "https://jsonplaceholder.typicode.com/todos/99999");        } catch (IOException e) {            System.out.println(e.getMessage());        }    }}

Типичные исключения

  • HttpConnectTimeoutException — не удалось подключиться за заданное время;
  • HttpTimeoutException — сервер не ответил за время timeout;
  • ConnectException — сервер недоступен;
  • UnknownHostException — неизвестное имя хоста, обычно проблема DNS;
  • SSLHandshakeException — проблема с сертификатом.

Все они являются наследниками IOException.

Асинхронные запросы

Метод sendAsync не блокирует поток и возвращает CompletableFuture:

import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.util.List;import java.util.concurrent.CompletableFuture;public class AsyncDemo {    public static void main(String[] args) {        HttpClient client = HttpClient.newHttpClient();        List<String> urls = List.of(            "https://jsonplaceholder.typicode.com/todos/1",            "https://jsonplaceholder.typicode.com/todos/2",            "https://jsonplaceholder.typicode.com/todos/3"        );        List<CompletableFuture<String>> futures = urls.stream()            .map(url -> HttpRequest.newBuilder().uri(URI.create(url)).build())            .map(req -> client.sendAsync(req, HttpResponse.BodyHandlers.ofString())                .thenApply(HttpResponse::body)                .exceptionally(e -> "ошибка: " + e.getMessage()))            .toList();        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();        futures.forEach(f -> System.out.println(f.join().replaceAll("\\s+", " ")));    }}

Три запроса выполняются параллельно, а не последовательно.

Другие типы тела ответа

import java.io.IOException;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.nio.file.Path;import java.util.stream.Stream;public class BodyHandlersDemo {    public static void main(String[] args) throws IOException, InterruptedException {        HttpClient client = HttpClient.newHttpClient();        HttpRequest request = HttpRequest.newBuilder()            .uri(URI.create("https://jsonplaceholder.typicode.com/todos"))            .build();        HttpResponse<Path> file = client.send(request, HttpResponse.BodyHandlers.ofFile(Path.of("todos.json")));        System.out.println(file.body());        HttpResponse<byte[]> bytes = client.send(request, HttpResponse.BodyHandlers.ofByteArray());        System.out.println(bytes.body().length);        HttpResponse<Stream<String>> lines = client.send(request, HttpResponse.BodyHandlers.ofLines());        System.out.println(lines.body().count());        HttpResponse<Void> discard = client.send(request, HttpResponse.BodyHandlers.discarding());        System.out.println(discard.statusCode());    }}

Для больших файлов используйте ofFile или ofInputStream, чтобы не загружать тело в память целиком.

Отправка файла:

HttpRequest upload = HttpRequest.newBuilder()    .uri(URI.create("https://example.com/upload"))    .header("Content-Type", "application/octet-stream")    .POST(HttpRequest.BodyPublishers.ofFile(Path.of("data.bin")))    .build();

Работа с JSON

В JDK нет встроенного JSON-парсера. Для простых случаев хватает ручной работы со строками, но на практике подключают библиотеку: Gson или Jackson. Пример с Gson (зависимость подключается через Maven или Gradle, как показано в главе про сборку):

import com.google.gson.Gson;import com.google.gson.reflect.TypeToken;import java.io.IOException;import java.lang.reflect.Type;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.util.List;public class JsonDemo {    record Todo(int userId, int id, String title, boolean completed) {    }    public static void main(String[] args) throws IOException, InterruptedException {        HttpClient client = HttpClient.newHttpClient();        Gson gson = new Gson();        HttpRequest one = HttpRequest.newBuilder()            .uri(URI.create("https://jsonplaceholder.typicode.com/todos/1"))            .build();        String body = client.send(one, HttpResponse.BodyHandlers.ofString()).body();        Todo todo = gson.fromJson(body, Todo.class);        System.out.println(todo.title());        HttpRequest all = HttpRequest.newBuilder()            .uri(URI.create("https://jsonplaceholder.typicode.com/todos?_limit=5"))            .build();        String listBody = client.send(all, HttpResponse.BodyHandlers.ofString()).body();        Type listType = new TypeToken<List<Todo>>() { }.getType();        List<Todo> todos = gson.fromJson(listBody, listType);        todos.forEach(t -> System.out.println(t.id() + ": " + t.title()));        String out = gson.toJson(new Todo(1, 201, "Новая задача", false));        System.out.println(out);    }}

Повтор запроса при ошибке

import java.io.IOException;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.time.Duration;public class Retry {    static HttpResponse<String> sendWithRetry(HttpClient client, HttpRequest request, int attempts)        throws IOException, InterruptedException {        IOException last = null;        for (int attempt = 1; attempt <= attempts; attempt++) {            try {                HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());                if (response.statusCode() < 500) {                    return response;                }                last = new IOException("HTTP " + response.statusCode());            } catch (IOException e) {                last = e;            }            Thread.sleep(Duration.ofMillis(200L * attempt * attempt));        }        throw last;    }    public static void main(String[] args) throws IOException, InterruptedException {        HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(3)).build();        HttpRequest request = HttpRequest.newBuilder()            .uri(URI.create("https://jsonplaceholder.typicode.com/todos/1"))            .timeout(Duration.ofSeconds(5))            .build();        System.out.println(sendWithRetry(client, request, 3).statusCode());    }}

Повторять стоит только безопасные для повтора запросы (GET и идемпотентные). Время ожидания между попытками увеличивают: это называется экспоненциальной задержкой.

Простой HTTP-сервер

В JDK есть небольшой встроенный сервер com.sun.net.httpserver (модуль jdk.httpserver). Подходит для учебных примеров и тестов:

import com.sun.net.httpserver.HttpExchange;import com.sun.net.httpserver.HttpServer;import java.io.IOException;import java.io.OutputStream;import java.net.InetSocketAddress;import java.nio.charset.StandardCharsets;public class SimpleServer {    public static void main(String[] args) throws IOException {        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);        server.createContext("/hello", exchange -> {            String name = "мир";            String query = exchange.getRequestURI().getQuery();            if (query != null && query.startsWith("name=")) {                name = query.substring(5);            }            respond(exchange, 200, "Привет, " + name + "!");        });        server.createContext("/echo", exchange -> {            if (!"POST".equals(exchange.getRequestMethod())) {                respond(exchange, 405, "Только POST");                return;            }            String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);            respond(exchange, 200, "Вы отправили: " + body);        });        server.start();        System.out.println("Сервер запущен: http://localhost:8080/hello");    }    static void respond(HttpExchange exchange, int status, String text) throws IOException {        byte[] bytes = text.getBytes(StandardCharsets.UTF_8);        exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=UTF-8");        exchange.sendResponseHeaders(status, bytes.length);        try (OutputStream out = exchange.getResponseBody()) {            out.write(bytes);        }    }}

Проверка из консоли:

curl "http://localhost:8080/hello?name=Anna"curl -X POST -d "тест" http://localhost:8080/echo

Для реальных веб-приложений применяют фреймворки: Spring Boot, Javalin, Micronaut, Quarkus.

Работа с URL

import java.net.URI;public class UriDemo {    public static void main(String[] args) {        URI uri = URI.create("https://user@example.com:8443/path/to/page?x=1&y=2#top");        System.out.println(uri.getScheme());        System.out.println(uri.getHost());        System.out.println(uri.getPort());        System.out.println(uri.getPath());        System.out.println(uri.getQuery());        System.out.println(uri.getFragment());        System.out.println(uri.resolve("../other"));    }}

Практические советы

  • Создавайте один HttpClient и переиспользуйте его.
  • Всегда задавайте таймауты соединения и ответа.
  • Проверяйте код статуса, а не только отсутствие исключения.
  • Кодируйте параметры через URLEncoder.
  • Не логируйте заголовки авторизации и тела с личными данными.
  • Для разбора JSON используйте проверенную библиотеку.
  • Учитывайте ограничения API: лимиты запросов (код 429) и заголовок Retry-After.

Итоговый проект

Напишите консольную утилиту «Погода»:

  1. Принимайте название города аргументом командной строки.
  2. Запрашивайте данные у открытого API (например, Open-Meteo, он не требует ключа).
  3. Разберите JSON-ответ с помощью Gson или Jackson.
  4. Выведите температуру и скорость ветра.
  5. Обработайте ошибки сети, неверный город и статусы 4xx и 5xx.
  6. Соберите проект через Maven или Gradle в исполняемый jar и покройте разбор ответа тестами JUnit.

Выполнив эту задачу, вы пройдете почти весь курс: работа со строками, коллекциями, исключениями, сетью, JSON, тестами и сборкой.