Overview

WebClient is an HTTP client for Helidon SE. It can be used to send requests and retrieve corresponding responses in a programmatic way.

Helidon WebClient provides the following features:

  • Blocking approach
    The Webclient uses the blocking approach to synchronously process a request and its correspond response. Both HTTP/1.1 and HTTP/2 request and response will run in the thread of the user. Additionally, for HTTP/2, virtual thread is employed to manage the connection.

  • Builder-like setup and execution
    Creates every client and request as a builder pattern. This improves readability and code maintenance.

  • Redirect chain
    Follows the redirect chain and perform requests on the correct endpoint by itself.

  • Tracing and security propagation
    Automatically propagates the configured tracing and security settings of the Helidon WebServer to the WebClient and uses them during request and response.

Maven Coordinates

To enable WebClient, add the following dependency to your project’s pom.xml (see Managing Dependencies).

<dependency>
    <groupId>io.helidon.webclient</groupId>
    <artifactId>helidon-webclient</artifactId>
</dependency>
Copied

The helidon-webclient dependency has built-in support for HTTP/1.1.

If support for HTTP/2 is a requirement, below dependency needs to be added:

<dependency>
    <groupId>io.helidon.webclient</groupId>
    <artifactId>helidon-webclient-http2</artifactId>
</dependency>
Copied

Usage

Instantiating the WebClient

You can create an instance of a WebClient by executing WebClient.create() which will have default settings and without a base uri set.

To change the default settings and register additional services, you can use simple builder that allows you to customize the client behavior.

Create a WebClient with simple builder:
WebClient client = WebClient.builder()
        .baseUri("http://localhost")
        .build();
Copied

Creating the Request

WebClient offers a set of request methods that are used to specify the type of action to be performed on a given resource. Below are some examples of request methods:

  • get()

  • post()

  • put()

  • method(Method method)

Check out HttpClient API to learn more about request methods. These methods will create a new instance of HttpClientRequest which can then be configured to add optional settings that will customize the behavior of the request.

Customizing the Request

Configuration can be set for every request type before it is sent.

Customizing a request
client.get()
        .uri("http://example.com") 
        .path("/path") 
        .queryParam("query", "parameter") 
        .fragment("someFragment") 
        .headers(headers -> headers.accept(MediaTypes.APPLICATION_JSON)); 
Copied
  • Overrides baseUri from WebClient
  • Adds path to the uri
  • Adds query parameter to the request
  • Adds fragment to the request
  • Adds header to the request

For more information about these optional parameters, check out ClientRequestBase API, which is a parent class of HttpClientRequest.

HttpClientRequest class also provides specific header methods that help the user to set a particular header. Some examples of these are:

  • contentType (MediaType contentType)

  • accept (MediaType…​ mediaTypes)

For more information about these methods, check out ClientRequest API, which is a parent class of HttpClientRequest.

Sending the Request

Once the request setup is completed, the following methods can be used to send it:

  • HttpClientResponse request()

  • <E> ClientResponseTyped<E> request(Class<E> type)

  • <E> E requestEntity(Class<E> type)

  • HttpClientResponse submit(Object entity)

  • <T> ClientResponseTyped<T> submit(Object entity, Class<T> requestedType)

  • HttpClientResponse outputStream(OutputStreamHandler outputStreamConsumer)

  • <T> ClientResponseTyped<T> outputStream(OutputStreamHandler outputStreamConsumer, Class<T> requestedType)

Each of the methods will provide a way to allow response to be retrieved in a particular response type. Refer to ClientRequest API for more details about these methods.

Execute a simple GET request to endpoint and receive a String response:
ClientResponseTyped<String> response = client.get()
        .path("/endpoint")
        .request(String.class);
String entityString = response.entity();
Copied

Protocol Used

WebClient currently supports HTTP/1.1 and HTTP/2 protocols. Below are the rules on which specific protocol will be used:

  • Using plain socket triggers WebClient to process a request using HTTP/1.1.

  • When using TLS, the client will use ALPN (protocol negotiation) to use appropriate HTTP version (either 1.1, or 2). HTTP/2 has a higher weight, so it is chosen if supported by both sides.

  • A specific protocol can be explicitly selected by calling HttpClientRequest#protocolId(String).

String result = client.get()
        .protocolId("http/1.1")
        .requestEntity(String.class);
  • If HTTP/2 is used, an upgrade attempt will be performed. If it fails, the client falls-back to HTTP/1.1.

  • The parameter prior-knowledge can be defined using HTTP/2 protocol configuration. Please refer to on how to customize HTTP/2. In such a case, prior-knowledge will be used and fail if it is unable to switch to HTTP/2.

Adding Media Support

Webclient supports the following built-in Helidon Media Support libraries:

  1. JSON Processing (JSON-P)
  2. JSON Binding (JSON-B)
  3. Jackson

They can be activated by adding their corresponding libraries into the classpath. This can simply be done by adding their corresponding dependencies.

Add JSON-P support:
<dependency>
    <groupId>io.helidon.http.media</groupId>
    <artifactId>helidon-http-media-jsonp</artifactId>
</dependency>
Copied
Add JSON-B support:
<dependency>
    <groupId>io.helidon.http.media</groupId>
    <artifactId>helidon-http-media-jsonb</artifactId>
</dependency>
Copied
Add Jackson support:
<dependency>
    <groupId>io.helidon.http.media</groupId>
    <artifactId>helidon-http-media-jackson</artifactId>
</dependency>
Copied

Users can also create their own Custom Media Support library and make them work by following either of the approaches:

  • Create a Provider of the Custom Media Support and expose it via Service Loader followed by adding the Media Support library to the classpath.

  • Explicitly register the Custom Media Support from WebClient.

WebClient.builder()
        .mediaContext(it -> it
                .addMediaSupport(CustomMediaSupport.create())) 
        .build();
Copied
  • Register CustomMedia support from the WebClient.

DNS Resolving

Webclient provides three DNS resolver implementations out of the box:

  • Java DNS resolution is the default.

  • First DNS resolution uses the first IP address from a DNS lookup. To enable this option, add below dependency:

<dependency>
    <groupId>io.helidon.webclient.dns.resolver</groupId>
    <artifactId>helidon-webclient-dns-resolver-first</artifactId>
</dependency>
Copied
  • Round-Robin DNS resolution cycles through IP addresses from a DNS lookup. To enable this option, add this dependency:

<dependency>
    <groupId>io.helidon.webclient.dns.resolver</groupId>
    <artifactId>helidon-webclient-dns-resolver-round-robin</artifactId>
</dependency>
Copied

Configuring the WebClient

The class responsible for WebClient configuration is:

Type: io.helidon.webclient.api.WebClient

This is a standalone configuration type, prefix from configuration root: clients

Configuration options

Optional configuration options
keytypedefault valuedescription
base-uri

ClientUri

 

Base uri used by the client in all requests.

connect-timeout

Duration

 

Connect timeout.

See io.helidon.common.socket.SocketOptions.connectTimeout()

connection-cache-size

int

256

Maximal size of the connection cache. For most HTTP protocols, we may cache connections to various endpoints for keep alive (or stream reuse in case of HTTP/2). This option limits the size. Setting this number lower than the "usual" number of target services will cause connections to be closed and reopened frequently.

content-encoding 

Configure the listener specific io.helidon.http.encoding.ContentEncodingContext. This method discards all previously registered ContentEncodingContext. If no content encoding context is registered, default encoding context is used.

cookie-manager 

WebClient cookie manager.

default-headers

Map<string, string>

 

Default headers to be used in every request from configuration.

follow-redirects

boolean

true

Whether to follow redirects.

keep-alive

boolean

true

Determines if connection keep alive is enabled (NOT socket keep alive, but HTTP connection keep alive, to re-use the same connection for multiple requests).

See io.helidon.common.socket.SocketOptions.socketKeepAlive()

max-in-memory-entity

int

131072

If the entity is expected to be smaller that this number of bytes, it would be buffered in memory to optimize performance. If bigger, streaming will be used.

Note that for some entity types we cannot use streaming, as they are already fully in memory (String, byte[]), for such cases, this option is ignored. Default is 128Kb.

max-redirects

int

10

Max number of followed redirects. This is ignored if followRedirects() option is false.

media-contextcreate()

Configure the listener specific io.helidon.http.media.MediaContext. This method discards all previously registered MediaContext. If no media context is registered, default media context is used.

media-type-parser-mode

ParserMode (STRICT, RELAXED)

ParserMode.STRICT

Configure media type parsing mode for HTTP Content-Type header.

properties

Map<string, string>

 

Properties configured for this client. These properties are propagated through client request, to be used by services (and possibly for other purposes).

protocol-configs

io.helidon.webclient.spi.ProtocolConfig[] (service provider interface)

 

Configuration of client protocols.

protocol-preference

string[]

 

List of HTTP protocol IDs by order of preference. If left empty, all discovered providers will be used, ordered by weight.

For example if both HTTP/2 and HTTP/1.1 providers are available (considering HTTP/2 has higher weights), for ALPN we will send h2 and http/1.1 and decide based on response. If TLS is not used, we would attempt an upgrade (or use prior knowledge if configured in protocolConfigs()).

proxy 

Proxy configuration to be used for requests.

read-continue-timeout

Duration

PT1S

Socket 100-Continue read timeout. Default is 1 second. This read timeout is used when 100-Continue is sent by the client, before it sends an entity.

read-timeout

Duration

 

Read timeout.

See io.helidon.common.socket.SocketOptions.readTimeout()

relative-uris

boolean

false

Can be set to true to force the use of relative URIs in all requests, regardless of the presence or absence of proxies or no-proxy lists.

send-expect-continue

boolean

true

Whether Expect-100-Continue header is sent to verify server availability before sending an entity.

Defaults to true.

services

io.helidon.webclient.spi.WebClientService[] (service provider interface)

 

WebClient services.

share-connection-cache

boolean

true

Whether to share connection cache between all the WebClient instances in JVM.

socket-options 

Socket options for connections opened by this client. If there is a value explicitly configured on this type and on the socket options, the one configured on this type’s builder will win:

  • readTimeout()

  • connectTimeout()

tls 

TLS configuration for any TLS request from this client. TLS can also be configured per request. TLS is used when the protocol is set to https.

write-buffer-size

int

4096

Buffer size used when writing data to the underlying socket on a client TCP connection. A value that is less or equal to 1 can be set to disable buffering at this level. Note that if writing data to the socket in small chunks, they may not be delivered to the network immediately due to Nagle’s algorithm (i.e., if TCP_NO_DELAY is turned off).

Protocol Specific Configuration

Protocol specific configuration can be set using the protocol-configs parameter. Webclient currently supports HTTP/1.1. and HTTP/2. Below are the options for each of the protocol type:

  • HTTP/1.1

Type: io.helidon.webclient.http1.Http1ClientProtocolConfig

Configuration options

Optional configuration options
keytypedefault valuedescription
default-keep-alive

boolean

true

Whether to use keep alive by default.

max-header-size

int

16384

Configure the maximum allowed header size of the response.

max-status-line-length

int

256

Configure the maximum allowed length of the status line from the response.

name

string

http_1_1
validate-request-headers

boolean

false

Sets whether the request header format is validated or not.

Defaults to `false` as user has control on the header creation.
validate-response-headers

boolean

true

Sets whether the response header format is validated or not.

Defaults to `true`.
  • HTTP/2

Type: io.helidon.webclient.http2.Http2ClientProtocolConfig

Configuration options

Optional configuration options
keytypedefault valuedescription
flow-control-block-timeout

Duration

PT15S

Timeout for blocking while waiting for window update when window is depleted.

initial-window-size

int

65535

Configure INITIAL_WINDOW_SIZE setting for new HTTP/2 connections. Sends to the server the size of the largest frame payload client is willing to receive. Defaults to 65535.

max-frame-size

int

16384

Configure initial MAX_FRAME_SIZE setting for new HTTP/2 connections. Maximum size of data frames in bytes the client is prepared to accept from the server. Default value is 2^14(16_384).

max-header-list-size

long

-1

Configure initial MAX_HEADER_LIST_SIZE setting for new HTTP/2 connections. Sends to the server the maximum header field section size client is prepared to accept. Defaults to -1, which means "unconfigured".

name

string

h2
ping

boolean

false

Check healthiness of cached connections with HTTP/2.0 ping frame. Defaults to false.

ping-timeout

Duration

PT0.5S

Timeout for ping probe used for checking healthiness of cached connections. Defaults to PT0.5S, which means 500 milliseconds.

prior-knowledge

boolean

false

Prior knowledge of HTTP/2 capabilities of the server. If server we are connecting to does not support HTTP/2 and prior knowledge is set to false, only features supported by HTTP/1 will be available and attempts to use HTTP/2 specific will throw an UnsupportedOperationException.

Plain text connection

If prior knowledge is set to true, we will not attempt an upgrade of connection and use prior knowledge. If prior knowledge is set to false, we will initiate an HTTP/1 connection and upgrade it to HTTP/2, if supported by the server. plaintext connection (h2c).

TLS protected connection

If prior knowledge is set to true, we will negotiate protocol using HTTP/2 only, failing if not supported. if prior knowledge is set to false, we will negotiate protocol using both HTTP/2 and HTTP/1, using the protocol supported by server.

Example of a WebClient Runtime Configuration

Config config = Config.create();
WebClient client = WebClient.builder()
        .baseUri("http://localhost")
        .config(config.get("client"))
        .build();
Copied

Example of a WebClient YAML Configuration

client:
  connect-timeout-millis: 2000
  read-timeout-millis: 2000
  follow-redirects: true 
  max-redirects: 5
  cookie-manager: 
    automatic-store-enabled: true
    default-cookies:
      flavor3: strawberry
      flavor4: raspberry
  default-headers: 
    Accept: '"application/json", "text/plain"'
  services: 
    metrics:
      - methods: ["PUT", "POST", "DELETE"]
        type: METER
        name-format: "client.meter.overall"
      - type: TIMER
        # meter per method
        name-format: "client.meter.%1$s"
      - methods: ["GET"]
        type: COUNTER
        errors: false
        name-format: "client.counter.%1$s.success"
        description: "Counter of successful GET requests"
      - methods: ["PUT", "POST", "DELETE"]
        type: COUNTER
        success: false
        name-format: "wc.counter.%1$s.error"
        description: "Counter of failed PUT, POST and DELETE requests"
    tracing:
  protocol-configs: 
    http_1_1:
      max-header-size: 20000
      validate-request-headers: true
    h2:
      prior-knowledge: true
  proxy: 
    host: "hostName"
    port: 80
    no-proxy: ["localhost:8080", ".helidon.io", "192.168.1.1"]
  tls: 
    trust:
      keystore:
        passphrase: "password"
        trust-store: true
        resource:
          resource-path: "client.p12"
Copied
  • Client functional settings
  • Cookie management
  • Default client headers
  • Client service configuration
  • Protocol configuration
  • Proxy configuration
  • TLS configuration

Examples

Webclient with Proxy

Configure Proxy setup either programmatically or via the Helidon configuration framework.

Configuring Proxy in your code

Proxy can be set directly from WebClient builder.

Proxy proxy = Proxy.builder()
        .type(Proxy.ProxyType.HTTP)
        .host(PROXY_HOST)
        .port(PROXY_PORT)
        .build();
WebClient.builder()
        .proxy(proxy)
        .build();
Copied

Alternative is to set proxy directly from the request via HttpClientRequest.

Proxy proxy = Proxy.create(); 
HttpClientResponse response = client.get("/proxiedresource")
        .proxy(proxy) 
        .request();
Copied
  • Proxy instance configured using system settings (environment variables and system properties)
  • Configure the proxy per client request

Configuring Proxy in the config file

Proxy can also be configured in WebClient through the application.yaml configuration file.

WebClient Proxy configuration in application.yaml
client:
  proxy:
    host: "hostName"
    port: 80
    no-proxy: ["localhost:8080", ".helidon.io", "192.168.1.1"]
Copied

Then, in your application code, load the configuration from that file.

WebClient initialization using the application.yaml file located on the classpath
Config config = Config.create(); 
WebClient.builder()
        .config(config.get("client")) 
        .build();
Copied
  • application.yaml is a default configuration source loaded when YAML support is on classpath, so we can just use Config.create()
  • Passing the client configuration node

WebClient TLS Setup

Configure TLS either programmatically or by the Helidon configuration framework.

Configuring TLS in your code

One way to configure TLS in WebClient is in your application code as shown below.

WebClient.builder()
        .tls(it -> it.trust(t -> t
                .keystore(k -> k.passphrase("password")
                        .trustStore(true)
                        .keystore(r -> r.resourcePath("client.p12")))))
        .build();
Copied

Configuring TLS in the config file

Another way to configure TLS in WebClient is through the application.yaml configuration file.

WebClient TLS configuration in application.yaml
client:
  tls:
    trust:
      keystore:
        passphrase: "password"
        trust-store: true
        resource:
          resource-path: "client.p12"
Copied

The passphrase value on the config file can be encrypted if stronger security is required. For more information on how secrets can be encrypted using a master password and store them in a configuration file, please see Configuration Secrets.

In the application code, load the settings from the configuration file.

WebClient initialization using the application.yaml file located on the classpath
Config config = Config.create(); 
WebClient.builder()
        .config(config.get("client")) 
        .build();
Copied
  • application.yaml is a default configuration source loaded when YAML support is on classpath, so we can just use Config.create()
  • Passing the client configuration node

Adding Service to WebClient

WebClient currently supports 3 built-in services namely metrics, tracing and security.

Enabling the service

In order for a service to function, their dependency needs to be added in the application’s pom.xml. Below are examples on how to enable the built-in services:

  • metrics

<dependency>
    <groupId>io.helidon.webclient</groupId>
    <artifactId>helidon-webclient-metrics</artifactId>
</dependency>
  • tracing

<dependency>
    <groupId>io.helidon.webclient</groupId>
    <artifactId>helidon-webclient-tracing</artifactId>
</dependency>
  • security

<dependency>
    <groupId>io.helidon.webclient</groupId>
    <artifactId>helidon-webclient-security</artifactId>
</dependency>

Adding a service in your code

Services can be added in WebClient as shown in the code below.

WebClientService clientService = WebClientMetrics.counter()
        .methods(Method.GET)
        .nameFormat("example.metric.%1$s.%2$s")
        .build(); 

WebClient.builder()
        .addService(clientService) 
        .build();
Copied
  • Creates new metric which will count all GET requests and has format of example.metric.GET.<host-name>
  • Register the service in the client instance

Adding service in the config file

Adding service in WebClient can also be done through the application.yaml configuration file.

WebClient Service configuration in application.yaml
webclient:
  services:
    metrics:
      - type: METER
        name-format: "client.meter.overall"
      - type: TIMER
        # meter per method
        name-format: "client.meter.%1$s"
      - methods: ["PUT", "POST", "DELETE"]
        type: COUNTER
        success: false
        name-format: "wc.counter.%1$s.error"
        description: "Counter of failed PUT, POST and DELETE requests"
    tracing:
Copied

Then, in your application code, load the configuration from that file.

WebClient initialization using the application.yaml file located on the classpath
Config config = Config.create(); 
WebClient.builder()
        .config(config.get("client")) 
        .build();
Copied
  • application.yaml is a default configuration source loaded when YAML support is on classpath, so we can just use Config.create()
  • Passing the client configuration node

Setting Protocol configuration

Individual protocols can be customized using the protocol-config parameter.

Setting up protocol configuration in your code

Below is an example of customizing HTTP/1.1 protocol in the application code.

WebClient.builder()
        .addProtocolConfig(Http1ClientProtocolConfig.builder()
                                   .defaultKeepAlive(false)
                                   .validateRequestHeaders(true)
                                   .validateResponseHeaders(false)
                                   .build())
        .build();
Copied

Setting up protocol configuration in the config file

Protocol configuration can also be set in the application.yaml configuration file.

Setting up HTTP/1.1 and HTTP/2 protocol using application.yaml file.
webclient:
  protocol-configs:
    http_1_1:
      max-header-size: 20000
      validate-request-headers: true
    h2:
      prior-knowledge: true
Copied

Then, in your application code, load the configuration from that file.

WebClient initialization using the application.yaml file located on the classpath
Config config = Config.create(); 
WebClient.builder()
        .config(config.get("client")) 
        .build();
Copied
  • application.yaml is a default configuration source loaded when YAML support is on classpath, so we can just use Config.create()
  • Passing the client configuration node

Context Propagation

WebClient supports the capability to propagate values from io.helidon.common.context.Context over HTTP headers.

To enable this feature (implemented as a WebClient service), add the following dependency to your pom file:

<dependency>
    <groupId>io.helidon.webclient</groupId>
    <artifactId>helidon-webclient-context</artifactId>
</dependency>
Copied

Example configuration:

client:
  services:
    context:
      records:
          # This looks up a `java.lang.String` context value classified `X-Helidon-Tid` and sends it as a `X-Helidon_Tid` header
        - header: "X-Helidon-Tid"
          # This looks up a `java.lang.String[]` context value classified with the classifier and sends it as a `X-Helidon-Cid` header, in case the value is not present, values "first" and "second" are sent instead
        - classifier: "io.helidon.webclient.context.propagation.cid"
          header: "X-Helidon-Cid"
          default-value: [ "first", "second" ]
          array: true
Copied

Full configuration reference:

WebClientContextService (webclient.context) Configuration

Type: io.helidon.webclient.context.WebClientContextService

Configuration options

Optional configuration options
keytypedefault valuedescription
records 

List of propagation records.

Reference