Guide to Calling HTTP APIs with HttpClient

Updated at 1780840809000
HttpClient is a synchronous HTTP client built on top of HttpURLConnection. Each request is represented by an object such as GetRequest, PostRequest, PutRequest, DeleteRequest, UploadRequest, or DownloadRequest, then passed to HttpClient for execution.
In principle, HttpClient opens a connection to the target URL, sets the HTTP method, timeout, and headers, serializes the request body based on Content-Type, reads the response, automatically decompresses gzip responses when the server returns Content-Encoding: gzip, and deserializes the response body based on the registered response type.
If the status code is lower than 400, the body is returned normally. If the status code is 400 or higher, the client throws a corresponding exception such as HttpBadRequestException, HttpNotFoundException, HttpUnauthorizedException, or HttpRequestException for unrecognized HTTP error codes.
By default, the client uses JSON for request and response bodies when Content-Type is not specified. The default connect and read timeouts are both 15_000ms.
sequenceDiagram
    participant App as Application
    participant Client as HttpClient
    participant Conn as HttpURLConnection
    participant API as HTTP API

    App->>Client: call(request) / request(request)
    Client->>Conn: Open connection
    Client->>Conn: Set method, headers, timeouts
    Client->>Conn: Serialize request body if needed
    Conn->>API: Send HTTP request
    API-->>Conn: Return HTTP response
    Conn-->>Client: Status, headers, body
    Client->>Client: Decompress gzip if needed
    Client->>Client: Deserialize body by response type

    alt Status code < 400
        Client-->>App: Return body or ResponseEntity
    else Status code >= 400
        Client-->>App: Throw HTTP exception
    end

Creating HttpClient

HttpClient client = HttpClient.builder()
    .connectTimeout(15_000)
    .readTimeout(15_000)
    .build();
You can also provide a custom ObjectMapper or body converter:
HttpClient client = HttpClient.builder()
    .objectMapper(objectMapper)
    .addBodyConverter("application/custom", customConverter)
    .build();

Calling an API and Returning the Body with call

Use call(Request request) when you only need the response body. If the API returns an HTTP error, this method throws the corresponding exception.
GetRequest request = new GetRequest()
    .setURL("http://localhost:8080/api/v1/users/1")
    .setResponseType(User.class);

User user = client.call(request);
POST JSON example:
CreateUserRequest body = new CreateUserRequest("Alice", 20);

PostRequest request = new PostRequest()
    .setURL("http://localhost:8080/api/v1/users")
    .setEntity(RequestEntity.body(body))
    .setResponseType(User.class);

User user = client.call(request);
Adding headers:
PostRequest request = new PostRequest()
    .setURL("http://localhost:8080/api/v1/users")
    .setEntity(
        RequestEntity.builder()
            .header("Authorization", "Bearer " + token)
            .body(body)
            .build()
    )
    .setResponseType(User.class);

User user = client.call(request);

Calling an API and Returning the Full Response with request

request(Request request) returns a ResponseEntity, including status code, headers, and body. Use this method when you need to inspect the status or response headers.
GetRequest request = new GetRequest()
    .setURL("http://localhost:8080/api/v1/users")
    .setResponseType(User[].class);

ResponseEntity response = client.request(request);

int status = response.getStatus();
Object body = response.getBody();
Unlike call, request does not automatically throw exceptions based on HTTP status. It returns the response as-is. If you want the same error-handling behavior as call, use getResponseBody.
ResponseEntity response = client.request(request);
User user = client.getResponseBody(response);

Low-Level API Call with request(HttpMethod, ...)

This method is useful when you want to pass the method, URL, entity, response type map, and timeout values directly without creating GetRequest, PostRequest, and similar request objects.
Map<Integer, Class<?>> responseTypes = new HashMap<>();
responseTypes.put(200, User.class);
responseTypes.put(201, User.class);
responseTypes.put(400, ErrorResponse.class);

RequestEntity entity = RequestEntity.builder()
    .header("Authorization", "Bearer " + token)
    .body(body)
    .build();

ResponseEntity response = client.request(
    HttpMethod.POST,
    "http://localhost:8080/api/v1/users",
    entity,
    responseTypes,
    15_000,
    15_000
);

User user = client.getResponseBody(response);

GET API

GetRequest request = new GetRequest()
    .setURL("http://localhost:8080/api/v1/users?limit=10")
    .setResponseType(User[].class);

User[] users = client.call(request);
GET requests do not send a body. If you need to send a token or other headers, set them through RequestEntity:
GetRequest request = new GetRequest()
    .setURL("http://localhost:8080/api/v1/profile")
    .setEntity(
        RequestEntity.builder()
            .header("Authorization", "Bearer " + token)
            .build()
    )
    .setResponseType(Profile.class);

Profile profile = client.call(request);

POST API

By default, the request body is sent as JSON.
PostRequest request = new PostRequest()
    .setURL("http://localhost:8080/api/v1/orders")
    .setEntity(RequestEntity.body(orderRequest))
    .setResponseType(Order.class);

Order order = client.call(request);
Sending form URL encoded data:
PostRequest request = new PostRequest()
    .setURL("http://localhost:8080/api/v1/login")
    .setEntity(
        RequestEntity.builder()
            .contentType(ContentTypes.APPLICATION_X_WWW_FORM_URLENCODED)
            .body(loginRequest)
            .build()
    )
    .setResponseType(LoginResponse.class);

LoginResponse response = client.call(request);

PUT API

PutRequest request = new PutRequest()
    .setURL("http://localhost:8080/api/v1/users/1")
    .setEntity(RequestEntity.body(updateUserRequest))
    .setResponseType(User.class);

User user = client.call(request);

DELETE API

DeleteRequest request = new DeleteRequest()
    .setURL("http://localhost:8080/api/v1/users/1")
    .setEntity(
        RequestEntity.builder()
            .header("Authorization", "Bearer " + token)
            .build()
    )
    .setResponseType(String.class);

String result = client.call(request);

Declaring Response Types by Status Code

By default, setResponseType(Class<?>) applies to 200 OK. If the API can return multiple success statuses or has a specific error response body, you can register response types by status code.
PostRequest request = new PostRequest()
    .setURL("http://localhost:8080/api/v1/users")
    .setEntity(RequestEntity.body(body))
    .setResponseType(200, User.class)
    .setResponseType(201, User.class)
    .setResponseType(400, ErrorResponse.class);

User user = client.call(request);

Uploading Files with callUpload

Use callUpload(UploadRequest request) when uploading a file and you only need the response body. If the server returns an HTTP error, this method throws the corresponding exception.
UploadRequest request = new UploadRequest()
    .setURL("http://localhost:8080/api/v1/files")
    .setFilePath("/tmp/avatar.png")
    .setFileName("avatar.png")
    .setResponseType(UploadResponse.class);

UploadResponse response = client.callUpload(request);
Uploading from an InputStream:
try (InputStream inputStream = Files.newInputStream(Path.of("/tmp/avatar.png"))) {
    UploadRequest request = new UploadRequest()
        .setURL("http://localhost:8080/api/v1/files")
        .setInputStream(inputStream)
        .setFileName("avatar.png")
        .setResponseType(UploadResponse.class);

    UploadResponse response = client.callUpload(request);
}

Uploading Files with upload

upload(UploadRequest request) returns a ResponseEntity, which is useful when you need to read the status or headers.
UploadRequest request = new UploadRequest()
    .setURL("http://localhost:8080/api/v1/files")
    .setFilePath("/tmp/report.pdf")
    .setResponseType(UploadResponse.class);

ResponseEntity response = client.upload(request);
You can use a cancellation token to cancel an upload in progress:
UploadCancellationToken token = new UploadCancellationToken();

UploadRequest request = new UploadRequest()
    .setURL("http://localhost:8080/api/v1/files")
    .setFilePath("/tmp/big-file.zip")
    .setResponseType(UploadResponse.class);

UploadResponse response = client.callUpload(request, token);

Downloading a File to a Directory

download(String fileURL, File storeLocation) downloads a file to the specified directory and returns the saved file name. The file name is taken from Content-Disposition if the server returns that header; otherwise, it is extracted from the URL.
String fileName = client.download(
    "http://localhost:8080/api/v1/files/report.pdf",
    new File("/tmp/downloads")
);
Use DownloadRequest when you need to add headers or configure timeouts:
DownloadRequest request = new DownloadRequest()
    .setFileURL("http://localhost:8080/api/v1/files/report.pdf")
    .setHeaders(
        MultiValueMap.builder()
            .setValue("Authorization", "Bearer " + token)
            .build()
    )
    .setConnectTimeout(15_000)
    .setReadTimeout(60_000);

String fileName = client.download(request, new File("/tmp/downloads"));

Downloading to an OutputStream

Use this overload when you want to decide where the data is written, such as a response stream, buffer, or manually managed file stream.
try (OutputStream outputStream = Files.newOutputStream(Path.of("/tmp/report.pdf"))) {
    client.download(
        "http://localhost:8080/api/v1/files/report.pdf",
        outputStream
    );
}

Downloading with a New File Name

The download(..., fileName) overload returns a DownloadFileResult, containing both the original file name and the new file name. If the original file has an extension, the client keeps that extension for the new file name.
DownloadFileResult result = client.download(
    "http://localhost:8080/api/v1/files/report.pdf",
    new File("/tmp/downloads"),
    "monthly-report"
);

// originalFileName: report.pdf
// fileName: monthly-report.pdf

Cancelling a Download

DownloadCancellationToken token = new DownloadCancellationToken();

DownloadRequest request = new DownloadRequest()
    .setFileURL("http://localhost:8080/api/v1/files/big-file.zip");

String fileName = client.download(
    request,
    new File("/tmp/downloads"),
    token
);
If the token is cancelled while downloading, the client throws DownloadCancelledException.

Error Handling

With call and callUpload, status codes from 400 and above are converted into exceptions:
try {
    User user = client.call(request);
} catch (HttpNotFoundException e) {
    // 404
} catch (HttpUnauthorizedException e) {
    // 401
} catch (HttpRequestException e) {
    // Other HTTP errors
}
With request and upload, you receive a ResponseEntity first, then you can inspect the status manually or call getResponseBody.
ResponseEntity response = client.request(request);

if (response.getStatus() >= 400) {
    client.getResponseBody(response);
}

Notes

If Content-Type is not specified, the request body is serialized as JSON. If no response type is declared, the body is usually read as a string, and the client will try to parse it into a Map when possible.
For production APIs, it is recommended to always call setResponseType so the result is explicit and easier to control.

Table Of Contents