Helidon WebClient is a comprehensive HTTP client library for Java microservices supporting HTTP/1.1, HTTP/2, and protocol negotiation with virtual thread support.
—
Client-side interceptors and middleware for adding cross-cutting concerns like authentication, logging, metrics, tracing, and custom request/response processing.
Functional interface for implementing client-side interceptors that can modify requests and responses.
/**
* Handle request/response processing in the service chain
* @param chain service chain for proceeding to next service or HTTP call
* @param clientRequest current client request
* @return service response
*/
WebClientServiceResponse handle(Chain chain, WebClientServiceRequest clientRequest);
/**
* Get service name for identification
* @return service name
*/
default String name();
/**
* Get service type for categorization
* @return service type
*/
default String type();
/**
* Service chain interface for proceeding through the interceptor chain
*/
interface Chain {
/**
* Proceed to next service in chain or execute HTTP request
* @param clientRequest request to process
* @return service response
*/
WebClientServiceResponse proceed(WebClientServiceRequest clientRequest);
}Usage Examples:
import io.helidon.webclient.spi.WebClientService;
// Simple logging service
public class LoggingService implements WebClientService {
@Override
public WebClientServiceResponse handle(Chain chain, WebClientServiceRequest request) {
System.out.println("Request: " + request.method() + " " + request.uri());
WebClientServiceResponse response = chain.proceed(request);
System.out.println("Response: " + response.status());
return response;
}
@Override
public String name() {
return "logging";
}
}
// Authentication service
public class AuthenticationService implements WebClientService {
private final String token;
public AuthenticationService(String token) {
this.token = token;
}
@Override
public WebClientServiceResponse handle(Chain chain, WebClientServiceRequest request) {
// Add authentication header
WebClientServiceRequest authenticatedRequest = request.toBuilder()
.header("Authorization", "Bearer " + token)
.build();
return chain.proceed(authenticatedRequest);
}
@Override
public String name() {
return "authentication";
}
}Register services with WebClient to automatically apply them to all requests.
/**
* Add a client service to the service chain
* @param service client service implementation
* @return builder instance
*/
WebClientConfig.Builder addService(WebClientService service);
/**
* Configure all client services
* @param services list of client services
* @return builder instance
*/
WebClientConfig.Builder services(List<WebClientService> services);Usage Examples:
// Register services during client creation
WebClient client = WebClient.builder()
.addService(new LoggingService())
.addService(new AuthenticationService(token))
.addService(new MetricsService())
.addService(new RetryService())
.build();
// All requests will go through the service chain
String response = client.get("/api/data").requestEntity(String.class);Request representation in the service chain with modification capabilities.
/**
* Service request interface providing access to request details
*/
public interface WebClientServiceRequest {
/**
* Get HTTP method
* @return HTTP method
*/
Method method();
/**
* Get target URI
* @return request URI
*/
ClientUri uri();
/**
* Get request headers
* @return headers instance
*/
ClientRequestHeaders headers();
/**
* Get request properties
* @return properties map
*/
Map<String, String> properties();
/**
* Create builder for modifying request
* @return request builder
*/
Builder toBuilder();
/**
* Builder for creating modified service requests
*/
interface Builder {
/**
* Set HTTP method
* @param method HTTP method
* @return builder instance
*/
Builder method(Method method);
/**
* Set target URI
* @param uri request URI
* @return builder instance
*/
Builder uri(ClientUri uri);
/**
* Add or modify header
* @param name header name
* @param values header values
* @return builder instance
*/
Builder header(String name, String... values);
/**
* Add request property
* @param name property name
* @param value property value
* @return builder instance
*/
Builder property(String name, String value);
/**
* Build modified request
* @return service request
*/
WebClientServiceRequest build();
}
}Response representation in the service chain with access to response details.
/**
* Service response interface providing access to response details
*/
public interface WebClientServiceResponse {
/**
* Get HTTP status
* @return response status
*/
Status status();
/**
* Get response headers
* @return response headers
*/
ClientResponseHeaders headers();
/**
* Get response entity
* @return readable entity
*/
ReadableEntity entity();
/**
* Get request that produced this response
* @return original request
*/
WebClientServiceRequest request();
/**
* Create builder for modifying response
* @return response builder
*/
Builder toBuilder();
/**
* Builder for creating modified service responses
*/
interface Builder {
/**
* Set HTTP status
* @param status response status
* @return builder instance
*/
Builder status(Status status);
/**
* Add or modify header
* @param name header name
* @param values header values
* @return builder instance
*/
Builder header(String name, String... values);
/**
* Set response entity
* @param entity response entity
* @return builder instance
*/
Builder entity(ReadableEntity entity);
/**
* Build modified response
* @return service response
*/
WebClientServiceResponse build();
}
}Complex service implementations demonstrating common patterns.
Retry Service:
public class RetryService implements WebClientService {
private final int maxRetries;
private final Duration delay;
public RetryService(int maxRetries, Duration delay) {
this.maxRetries = maxRetries;
this.delay = delay;
}
@Override
public WebClientServiceResponse handle(Chain chain, WebClientServiceRequest request) {
WebClientServiceResponse response = null;
Exception lastException = null;
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
response = chain.proceed(request);
// Retry on server errors (5xx)
if (response.status().code() < 500) {
return response;
}
if (attempt < maxRetries) {
Thread.sleep(delay.toMillis());
}
} catch (Exception e) {
lastException = e;
if (attempt < maxRetries) {
try {
Thread.sleep(delay.toMillis());
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
if (response != null) {
return response;
}
throw new RuntimeException("Request failed after " + maxRetries + " retries", lastException);
}
@Override
public String name() {
return "retry";
}
}Metrics Service:
public class MetricsService implements WebClientService {
private final MeterRegistry meterRegistry;
public MetricsService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Override
public WebClientServiceResponse handle(Chain chain, WebClientServiceRequest request) {
Timer.Sample sample = Timer.start(meterRegistry);
try {
WebClientServiceResponse response = chain.proceed(request);
// Record metrics
Timer.builder("http.client.requests")
.tag("method", request.method().text())
.tag("uri", request.uri().path())
.tag("status", String.valueOf(response.status().code()))
.tag("outcome", response.status().code() < 400 ? "SUCCESS" : "ERROR")
.register(meterRegistry)
.record(sample.stop());
return response;
} catch (Exception e) {
Timer.builder("http.client.requests")
.tag("method", request.method().text())
.tag("uri", request.uri().path())
.tag("status", "UNKNOWN")
.tag("outcome", "ERROR")
.register(meterRegistry)
.record(sample.stop());
throw e;
}
}
@Override
public String name() {
return "metrics";
}
}Circuit Breaker Service:
public class CircuitBreakerService implements WebClientService {
private final CircuitBreaker circuitBreaker;
public CircuitBreakerService(CircuitBreaker circuitBreaker) {
this.circuitBreaker = circuitBreaker;
}
@Override
public WebClientServiceResponse handle(Chain chain, WebClientServiceRequest request) {
return circuitBreaker.executeSupplier(() -> {
WebClientServiceResponse response = chain.proceed(request);
// Consider 5xx responses as failures
if (response.status().code() >= 500) {
throw new RuntimeException("Server error: " + response.status());
}
return response;
});
}
@Override
public String name() {
return "circuit-breaker";
}
}Request/Response Transformation Service:
public class TransformationService implements WebClientService {
@Override
public WebClientServiceResponse handle(Chain chain, WebClientServiceRequest request) {
// Transform request - add custom headers
WebClientServiceRequest transformedRequest = request.toBuilder()
.header("X-Request-ID", UUID.randomUUID().toString())
.header("X-Timestamp", Instant.now().toString())
.property("transformation.applied", "true")
.build();
WebClientServiceResponse response = chain.proceed(transformedRequest);
// Transform response - add custom header
return response.toBuilder()
.header("X-Processed-By", "TransformationService")
.build();
}
@Override
public String name() {
return "transformation";
}
}Service provider for automatic service discovery and registration.
/**
* Service provider for WebClient services
*/
public interface WebClientServiceProvider {
/**
* Create service instance from configuration
* @param config configuration
* @param name service name
* @return service instance
*/
WebClientService create(Config config, String name);
}Usage Examples:
// Implement service provider for automatic discovery
public class LoggingServiceProvider implements WebClientServiceProvider {
@Override
public WebClientService create(Config config, String name) {
return new LoggingService(config.get("level").asString().orElse("INFO"));
}
}
// Register via META-INF/services/io.helidon.webclient.spi.WebClientServiceProviderServices are executed in the order they are registered, forming a chain where each service can:
chain.proceed()Service Chain Execution Order:
// Services registered in this order
WebClient client = WebClient.builder()
.addService(new AuthenticationService()) // Executes first
.addService(new LoggingService()) // Executes second
.addService(new MetricsService()) // Executes third
.build();
// Execution flow:
// 1. AuthenticationService.handle() -> adds auth header
// 2. LoggingService.handle() -> logs request
// 3. MetricsService.handle() -> starts timer
// 4. HTTP request executed
// 5. MetricsService.handle() <- records metrics
// 6. LoggingService.handle() <- logs response
// 7. AuthenticationService.handle() <- processes response@FunctionalInterface
public interface WebClientService extends NamedService {
WebClientServiceResponse handle(Chain chain, WebClientServiceRequest clientRequest);
default String name();
default String type();
interface Chain {
WebClientServiceResponse proceed(WebClientServiceRequest clientRequest);
}
}
public interface WebClientServiceRequest {
Method method();
ClientUri uri();
ClientRequestHeaders headers();
Map<String, String> properties();
Builder toBuilder();
interface Builder {
Builder method(Method method);
Builder uri(ClientUri uri);
Builder header(String name, String... values);
Builder property(String name, String value);
WebClientServiceRequest build();
}
}
public interface WebClientServiceResponse {
Status status();
ClientResponseHeaders headers();
ReadableEntity entity();
WebClientServiceRequest request();
Builder toBuilder();
interface Builder {
Builder status(Status status);
Builder header(String name, String... values);
Builder entity(ReadableEntity entity);
WebClientServiceResponse build();
}
}
public interface WebClientServiceProvider {
WebClientService create(Config config, String name);
}Install with Tessl CLI
npx tessl i tessl/maven-io-helidon-webclient--helidon-webclient