Guide to Calling HTTP APIs with HttpClientProxy
Updated at 1780841105000HttpClientProxy is a wrapper around HttpClient that supports two ways of calling HTTP APIs: synchronous calls with timeout and asynchronous calls with callbacks. For normal HTTP requests, the proxy does not execute the request immediately. Instead, it pushes the request into a queue. Worker threads then take requests from the queue, execute them through HttpClient, and complete the corresponding future or callback.
sequenceDiagram
participant App as Application
participant Proxy as HttpClientProxy
participant Queue as RequestQueue
participant Worker as Worker thread
participant Client as HttpClient
participant API as HTTP API
App->>Proxy: call/request/fire/execute(request)
Proxy->>Queue: add request
Worker->>Queue: take request
Worker->>Client: client.request(request)
Client->>API: HTTP request
API-->>Client: HTTP response
Client-->>Worker: ResponseEntity
Worker-->>Proxy: complete future/callback
Proxy-->>App: body / ResponseEntity / callback
Creating HttpClientProxy
You can create a proxy using the builder:
HttpClientProxy client = HttpClientProxy.builder()
.threadPoolSize(8)
.requestQueueCapacity(10_000)
.connectTimeout(15_000)
.readTimeout(15_000)
.autoStart(true)
.build();
If
autoStart(true) is not enabled, call start() before sending requests:
HttpClientProxy client = HttpClientProxy.builder()
.threadPoolSize(8)
.requestQueueCapacity(10_000)
.build();
client.start();
When the proxy is no longer needed, close it to stop worker threads, clear the queue, and cancel unfinished requests:
client.close(); // or client.stop();
Note:
call, request, fire, and execute go through the HttpClientProxy queue. The download and upload methods currently delegate directly to HttpClient, so they run synchronously on the calling thread.Creating A Request
Basic request types include
GetRequest, PostRequest, PutRequest, and DeleteRequest. A request can define its URL, body, headers, timeout, and response type by status code.GET example:
GetRequest request = new GetRequest() .setURL("https://api.example.com/users/1") .setResponseType(UserResponse.class);
POST JSON example:
CreateUserRequest body = new CreateUserRequest("Alice"); PostRequest request = new PostRequest() .setURL("https://api.example.com/users") .setEntity( RequestEntity.builder() .contentType("application/json") .header("Authorization", "Bearer " + accessToken) .body(body) .build() ) .setResponseType(201, UserResponse.class);
If
Content-Type is not specified, the request body is serialized as JSON by default. The response body is deserialized based on the response Content-Type. If setResponseType(statusCode, SomeClass.class) is configured, the body for that status code is parsed into the given class.call: Call An API And Return The Body
call(Request request, int timeout) sends the request through the queue, waits up to timeout, and returns the response body.
UserResponse user = client.call(
new GetRequest()
.setURL("https://api.example.com/users/1")
.setResponseType(UserResponse.class),
5_000
);
Use
call when you only need the response body and want HTTP error statuses to be converted into exceptions. For status codes below 400, the method returns the body. For status codes from 400, it throws the corresponding exception, such as bad request, unauthorized, not found, conflict, too many requests, or internal server error.request: Call An API And Return ResponseEntity
request(Request request, int timeout) returns the full ResponseEntity, including status, headers, and body.
ResponseEntity response = client.request(
new GetRequest()
.setURL("https://api.example.com/users/1")
.setResponseType(UserResponse.class),
5_000
);
int status = response.getStatus();
Object body = response.getBody();
Use
request when you need to inspect the status code or headers, such as pagination, rate limit, Location, or ETag headers.fire: Asynchronous Call Returning Body
fire(Request request, RequestCallback callback) sends the request through the queue and returns the result through a callback. The callback receives the response body, not the full ResponseEntity.
client.fire(
new GetRequest()
.setURL("https://api.example.com/users/1")
.setResponseType(UserResponse.class),
new RequestCallback<UserResponse>() {
@Override
public void onResponse(UserResponse response) {
System.out.println("User: " + response.getName());
}
@Override
public void onException(Exception e) {
e.printStackTrace();
}
}
);
Use
fire when you want a non-blocking API call and only care about the response body.execute: Asynchronous Call Returning ResponseEntity
execute(Request request, RequestCallback<ResponseEntity> callback) is similar to fire, but the callback receives the full ResponseEntity.
client.execute(
new GetRequest()
.setURL("https://api.example.com/users/1")
.setResponseType(UserResponse.class),
new RequestCallback<ResponseEntity>() {
@Override
public void onResponse(ResponseEntity response) {
System.out.println("Status: " + response.getStatus());
System.out.println("Body: " + response.getBody());
}
@Override
public void onException(Exception e) {
e.printStackTrace();
}
}
);
Use
execute when you need to process the status code, headers, and body in the callback.Download A File By URL
The simplest download method downloads a file from a URL into a directory. The file name is taken from the
Content-Disposition header. If that header is not available, the name is derived from the URL.
String fileName = client.download(
"https://example.com/files/report.pdf",
new File("/tmp/downloads")
);
System.out.println("Downloaded: " + fileName);
You can pass a
DownloadCancellationToken to cancel the download:DownloadCancellationToken token = new DownloadCancellationToken(); String fileName = client.download( "https://example.com/files/report.pdf", new File("/tmp/downloads"), token ); // from another thread token.cancel();
Download With DownloadRequest
Use
DownloadRequest when you need custom timeout or headers.DownloadRequest request = new DownloadRequest() .setFileURL("https://example.com/files/report.pdf") .setConnectTimeout(5_000) .setReadTimeout(30_000); String fileName = client.download( request, new File("/tmp/downloads") );
Download Into OutputStream
If you do not want to save directly to a file, you can write the response into an
OutputStream.try (OutputStream outputStream = Files.newOutputStream(Paths.get("/tmp/report.pdf"))) { client.download( "https://example.com/files/report.pdf", outputStream ); }
This variant also supports
DownloadRequest and DownloadCancellationToken.Download With A Custom File Name
When a custom
fileName is provided, the client saves the file using the new name while keeping the original extension if it can be detected.
DownloadFileResult result = client.download(
"https://example.com/files/monthly-report.pdf",
new File("/tmp/downloads"),
"report-2026-06"
);
System.out.println(result.getOriginalFileName()); // monthly-report.pdf
System.out.println(result.getNewFileName()); // report-2026-06.pdf
This variant returns
DownloadFileResult, which contains both the original file name and the new file name.callUpload: Upload A File And Return The Body
callUpload(UploadRequest request) uploads a file using multipart form-data and returns the response body.UploadRequest request = new UploadRequest() .setURL("https://api.example.com/files") .setFilePath("/tmp/report.pdf") .setFileName("report.pdf") .setResponseType(UploadResponse.class); UploadResponse response = client.callUpload(request);
You can also upload from an
InputStream:try (InputStream inputStream = Files.newInputStream(Paths.get("/tmp/report.pdf"))) { UploadRequest request = new UploadRequest() .setURL("https://api.example.com/files") .setInputStream(inputStream) .setFileName("report.pdf") .setResponseType(UploadResponse.class); UploadResponse response = client.callUpload(request); }
The file is sent as a multipart field named
file.upload: Upload A File And Return ResponseEntity
upload(UploadRequest request) returns the full response.
ResponseEntity response = client.upload(
new UploadRequest()
.setURL("https://api.example.com/files")
.setFilePath("/tmp/report.pdf")
.setFileName("report.pdf")
.setResponseType(UploadResponse.class)
);
System.out.println(response.getStatus());
System.out.println(response.getBody());
You can pass an
UploadCancellationToken to cancel the upload:UploadCancellationToken token = new UploadCancellationToken(); ResponseEntity response = client.upload( new UploadRequest() .setURL("https://api.example.com/files") .setFilePath("/tmp/report.pdf") .setFileName("report.pdf") .setResponseType(UploadResponse.class), token ); // from another thread token.cancel();
Usage Notes
HttpClientProxy must be active before calling call, request, fire, or execute. If it has not been started or has already been closed, the proxy throws a client-not-active error.The request queue has a fixed capacity. When the number of waiting requests exceeds
requestQueueCapacity, new requests are rejected with a queue-full error.For
call and request, the timeout parameter is the maximum time to wait for the future result. HTTP connect timeout and read timeout are configured separately through the builder or on each request.A typical lifecycle looks like this:
try { // call APIs } finally { client.close(); }