Configure the internal logger and verbosity.
HttpClient client = HttpClient.builder()
.logger(new MyLogger())
.logLevel(LoggerLevel.HEADERS)
.build();Implement AuthChallengeHandler to handle 401 Unauthorized responses. The client will retry the request exactly once if the handler returns true.
HttpClient client = HttpClient.builder()
.authChallengeHandler((clientInstance, response) -> {
// Refresh token logic here
tokenService.refreshToken();
return true; // Retry request
})
.build();Using a Supplier<String> for headers ensures they are evaluated at the exact moment the request is sent. If an AuthChallengeHandler triggers a retry, the supplier is called again, picking up any updated values (like a refreshed JWT).
client.get(url)
.authorization(() -> tokenService.getToken()) // Lazy evaluation
.execute();The SerializerProvider interface handles both directions of JSON mapping:
- Request: Pass a POJO to
.json(object)to have it automatically serialized to JSON. - Response: Use
.as(Class)to automatically deserialize the JSON response to a Java object.
Register your provider in META-INF/services/j8a.http.spi.SerializerProvider for automatic discovery.
Interceptors allow you to hook into the request/response lifecycle. Common use cases include global headers, caching, or custom logging.
HttpClient client = HttpClient.builder()
.interceptor(chain -> {
// Modify request
RequestSpecification modified = chain.request().toBuilder()
.header("X-Custom-Global", "Value")
.build();
// Proceed down the chain
Response response = chain.proceed(modified);
// Inspect response
System.out.println("Status: " + response.getStatusCode());
return response;
})
.build();Standard Java 8 HttpURLConnection does not support PATCH. This library implements a reflection-based workaround to enable it.
Note: On Java 9+, you must add --add-opens java.base/java.net=ALL-UNNAMED to your JVM arguments to allow this reflection.
Use this base class to create specialized clients for your services. It handles base URLs, default headers, and centralized error handling.
This example shows a wrapper that adds a custom API key header to every request and maps 404 errors to a specific exception.
public class UserServiceClient extends AbstractApiClient {
public UserServiceClient(HttpClient httpClient) {
super(httpClient, "https://api.myapp.com/v1");
}
@Override
protected Map<String, List<String>> defaultHeaders() {
return new HeadersBuilder()
.set("X-API-Key", "secret-key-123")
.set("Accept", "application/json")
.build();
}
@Override
protected void handleError(Response response) {
if (response.getStatusCode() == 404) {
throw new UserNotFoundException("User not found in MyApp");
}
if (response.isError()) {
throw new RuntimeException("API Error: " + response.getStatusCode());
}
}
public User getUser(String id) {
// Automatically uses baseUrl and injects default headers
return get("/users/" + id).as(User.class);
}
public void saveUser(User user) {
post("/users", user).execute();
}
}