Customizable HTTP Client
Some LangChain4j modules (including OpenAI, Ollama and the MCP Streamable HTTP transport) support customizing the HTTP clients used to call remote APIs.
The langchain4j-http-client module implements an HttpClient SPI, which is used
by those modules to call remote APIs.
This means the underlying HTTP client can be customized,
and any other HTTP client can be integrated by implementing the HttpClient SPI.
Currently, there are the following out-of-the-box implementations:
JdkHttpClientfrom thelangchain4j-http-client-jdkmodule. It is the fallback when a supported module (e.g.,langchain4j-open-ai) is used without another implementation.SpringRestClientfrom thelangchain4j-http-client-spring-boot4-restclient/langchain4j-http-client-spring-restclientmodules. It is used by default when a supported module's Spring Boot starter (e.g.,langchain4j-open-ai-spring-boot4-starter/langchain4j-open-ai-spring-boot-starter) is used.ApacheHttpClientfrom thelangchain4j-http-client-apachemodule.OkHttpClientfrom thelangchain4j-http-client-okhttpmodule.
Selecting an HTTP client
When no builder or client is supplied explicitly, the supported modules discover
HttpClientBuilderFactory implementations on the classpath. The JDK client is a fallback:
| Available implementations | Selected client |
|---|---|
| Only JDK | JDK |
| Only one other client | That client |
| JDK and one other client | The other client |
| Multiple other clients with the same highest priority | Conflict error |
Factories can override priority() to influence selection: higher values win. The default is
0; the JDK factory uses PrioritizedFactory.YIELDS_TO_OTHERS (-100). A tie at the highest
priority fails instead of depending on classpath order.
The langchain4j.http.clientBuilderFactory system property overrides priority. Its value must be
the fully qualified class name of an available factory. For example, to keep using JDK when another
implementation is present:
-Dlangchain4j.http.clientBuilderFactory=dev.langchain4j.http.client.jdk.JdkHttpClientBuilderFactory
Passing a builder explicitly bypasses discovery and this system property.
The MCP Streamable HTTP transport follows different rules: see Custom HTTP client.
Customizing JDK's HttpClient
HttpClient.Builder httpClientBuilder = HttpClient.newBuilder()
.sslContext(...);
JdkHttpClientBuilder jdkHttpClientBuilder = JdkHttpClient.builder()
.httpClientBuilder(httpClientBuilder);
OpenAiChatModel model = OpenAiChatModel.builder()
.httpClientBuilder(jdkHttpClientBuilder)
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
HTTP version
By default, JdkHttpClient uses HTTP/2 for https:// URLs (when the server supports it) and HTTP/1.1 for
plain http:// URLs. HTTP/2 over plain HTTP would require an Upgrade: h2c request, which many servers
do not handle correctly. For example, Python servers running on uvicorn with the httptools parser
(such as vLLM or Xinference) ignore the request body, and Java servers with HTTP/2 enabled on Tomcat or
Reactor Netty reject larger requests or stop responding.
Typical symptoms are a server error saying the request body is missing (e.g., Field required),
Unsupported upgrade request, 413 Request Entity Too Large, or a request that hangs until it times out.
Before version 1.23.0, JdkHttpClient used HTTP/2 for plain http:// URLs as well.
If you use an older version and see the symptoms above, provide an HttpClient.Builder
with .version(HttpClient.Version.HTTP_1_1) as shown below.
When you provide your own HttpClient.Builder (for example, to configure a proxy or an SSLContext),
the HTTP version configured on it is used for all URLs. If you do not set a version on it, the JDK default
is HTTP/2, so plain http:// requests send the Upgrade: h2c request again.
To use HTTP/1.1 in that case, set the version explicitly:
HttpClient.Builder httpClientBuilder = HttpClient.newBuilder()
.proxy(...)
.version(HttpClient.Version.HTTP_1_1);
JdkHttpClientBuilder jdkHttpClientBuilder = JdkHttpClient.builder()
.httpClientBuilder(httpClientBuilder);
Conversely, if your plain-HTTP server supports the Upgrade: h2c request and you want to use HTTP/2 with it,
provide a builder with .version(HttpClient.Version.HTTP_2).
An HttpClient implementation can also provide non-blocking counterparts: executeAsync(...) for a single
response and stream(...) for a cold Flow.Publisher of parsed server-sent events. The bundled JDK, OkHttp and
Apache clients implement both, and so does SpringRestClient when spring-webflux is on the classpath.
See Non-blocking and Reactive.
Customizing Spring's RestClient
SpringRestClientBuilder springRestClientBuilder = SpringRestClient.builder()
.restClientBuilder(RestClient.builder())
.clientHttpRequestFactoryBuilder(ClientHttpRequestFactoryBuilder.httpComponents())
.streamingRequestExecutor(new VirtualThreadTaskExecutor());
OpenAiChatModel model = OpenAiChatModel.builder()
.httpClientBuilder(springRestClientBuilder)
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();
SpringRestClient sends blocking requests with RestClient and non-blocking requests (executeAsync(...) and
stream(...)) with WebClient, which needs spring-webflux on the classpath. Because spring-webflux is optional,
the WebClient.Builder is passed in a WebClientBuilderHolder:
SpringRestClientBuilder springRestClientBuilder = SpringRestClient.builder()
.restClientBuilder(RestClient.builder())
.webClientBuilder(WebClientBuilderHolder.of(WebClient.builder()))
.clientHttpConnectorBuilder(ClientHttpConnectorBuilder.reactor()); // optional: pins the connector
SpringRestClient builds the request factory and the connector itself, to apply its timeouts. So a request factory
set on the RestClient.Builder, or a connector set on the WebClient.Builder, is not used: choose the HTTP client
with clientHttpRequestFactoryBuilder(...) and clientHttpConnectorBuilder(...) instead.
To apply Spring Boot's HTTP client settings, such as the SSL bundle and redirects, inject the settings beans that
Spring Boot configures and pass them on: HttpClientSettings with httpClientSettings(...) on Spring Boot 4, or
ClientHttpRequestFactorySettings with clientHttpRequestFactorySettings(...) and ClientHttpConnectorSettings
with clientHttpConnectorSettings(...) on Spring Boot 3. The timeouts in these settings are not used by LangChain4j
models, which always set their own: set the timeout on the model builder instead.
HTTP version with the JDK client
When the JDK HttpClient is used, whether Spring picks it (no Apache HttpComponents, Jetty or Reactor Netty on the
classpath) or you set it with clientHttpRequestFactoryBuilder(...) or clientHttpConnectorBuilder(...),
SpringRestClient behaves like JdkHttpClient: plain http:// requests are sent over HTTP/1.1, and https://
requests use HTTP/2 when the server supports it. This applies to both blocking and non-blocking requests.
See HTTP version for why plain HTTP does not use HTTP/2.
Before version 1.23.0-beta33, SpringRestClient used HTTP/2 for plain http:// URLs as well.
If you use an older version and see the symptoms described in HTTP version,
set the version explicitly:
SpringRestClientBuilder springRestClientBuilder = SpringRestClient.builder()
.clientHttpRequestFactoryBuilder(ClientHttpRequestFactoryBuilder.jdk()
.withHttpClientCustomizer(builder -> builder.version(HttpClient.Version.HTTP_1_1)))
.clientHttpConnectorBuilder(ClientHttpConnectorBuilder.jdk() // only needed for non-blocking requests
.withHttpClientCustomizer(builder -> builder.version(HttpClient.Version.HTTP_1_1)));
Customizing Apache's HttpClient
org.apache.hc.client5.http.impl.classic.HttpClientBuilder httpClientBuilder = org.apache.hc.client5.http.impl.classic.HttpClientBuilder.create();
ApacheHttpClientBuilder apacheHttpClientBuilder = ApacheHttpClient.builder()
.httpClientBuilder(httpClientBuilder);
OpenAiChatModel model = OpenAiChatModel.builder()
.httpClientBuilder(apacheHttpClientBuilder)
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.build();