Hi,
I found out that in JDK 17 and 21 (probably other versions too) when I call RestTemplateBuilder
customization of connectionTimeout
and requestFactory
it returns exception as follows.
Seems as bug to me.
Thx
Ivos
Code
package cz.publicstaticvoidmain.reproduce;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import java.net.http.HttpClient;
import java.time.Duration;
@SpringBootApplication
public class ReproduceApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ReproduceApplication.class);
// Do not start HTTP stack
app.setWebApplicationType(WebApplicationType.NONE);
app.run(args);
}
}
@Component
class ReproduceComponent {
public ReproduceComponent(
RestTemplateBuilder restTemplateBuilder
) {
restTemplateBuilder
.requestFactory(() -> new JdkClientHttpRequestFactory(HttpClient.newBuilder().build()))
.connectTimeout(Duration.ofMinutes(5))
.build();
}
}
and it throws exception
2025-06-04T16:22:01.515+02:00 ERROR 28788 --- [spring-boot-resttemplatebuilder-timeout-reproduce] [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'reproduceComponent' defined in file [C:\dev\IdeaProjects\spring-boot-resttemplatebuilder-timeout-reproduce\target\classes\cz\publicstaticvoidmain\reproduce\ReproduceComponent.class]: Failed to instantiate [cz.publicstaticvoidmain.reproduce.ReproduceComponent]: Constructor threw exception
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:321) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:309) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1222) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1188) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) ~[spring-context-6.2.7.jar:6.2.7]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) ~[spring-context-6.2.7.jar:6.2.7]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:753) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.5.0.jar:3.5.0]
at cz.publicstaticvoidmain.reproduce.ReproduceApplication.main(ReproduceApplication.java:20) ~[classes/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [cz.publicstaticvoidmain.reproduce.ReproduceComponent]: Constructor threw exception
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:222) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:145) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:318) ~[spring-beans-6.2.7.jar:6.2.7]
... 18 common frames omitted
Caused by: java.lang.IllegalStateException: Request factory org.springframework.http.client.JdkClientHttpRequestFactory does not have a suitable setConnectTimeout method
at org.springframework.util.Assert.state(Assert.java:101) ~[spring-core-6.2.7.jar:6.2.7]
at org.springframework.boot.http.client.ReflectiveComponentsClientHttpRequestFactoryBuilder.findMethod(ReflectiveComponentsClientHttpRequestFactoryBuilder.java:124) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.http.client.ReflectiveComponentsClientHttpRequestFactoryBuilder.setConnectTimeout(ReflectiveComponentsClientHttpRequestFactoryBuilder.java:106) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.http.client.ReflectiveComponentsClientHttpRequestFactoryBuilder.lambda$configure$1(ReflectiveComponentsClientHttpRequestFactoryBuilder.java:82) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.context.properties.PropertyMapper$Source.to(PropertyMapper.java:294) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.http.client.ReflectiveComponentsClientHttpRequestFactoryBuilder.configure(ReflectiveComponentsClientHttpRequestFactoryBuilder.java:82) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.http.client.ReflectiveComponentsClientHttpRequestFactoryBuilder.build(ReflectiveComponentsClientHttpRequestFactoryBuilder.java:71) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.web.client.RestTemplateBuilder.buildRequestFactory(RestTemplateBuilder.java:754) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.web.client.RestTemplateBuilder.configure(RestTemplateBuilder.java:720) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.web.client.RestTemplateBuilder.build(RestTemplateBuilder.java:695) ~[spring-boot-3.5.0.jar:3.5.0]
at cz.publicstaticvoidmain.reproduce.ReproduceComponent.<init>(ReproduceApplication.java:34) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481) ~[na:na]
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:209) ~[spring-beans-6.2.7.jar:6.2.7]
... 20 common frames omitted
Comment From: wilkinsona
This isn't a bug. The JDK client does not support configuration of its connect timeout, only its read timeout. As a result, the exception is thrown as your combination of custom request factory and connect timeout is not valid.
Comment From: bedla
I can see java.net.http.HttpClient.Builder#connectTimeout
method in the src.
Am I missing something?
I can investigate it more and prepare PR, if you want :)
Comment From: wilkinsona
Am I missing something?
Not really. I was a little unclear above. I was referring to JdkClientHttpRequestFactory
rather than the underlying HttpClient
. It's the former that has no setter for the connect timeout.
I can investigate it more and prepare PR, if you want :)
Thanks for the offer. Such a PR would have to be made against Spring Framework as that's where JdkClientHttpRequestFactory
is maintained.
Comment From: bedla
I see. :) I will create PR in Spring Framework itself - sorry, I did not checked originator dependnecy and thought that it come from Boot.
Comment From: bedla
I was digging more deeper and I think that there is no need to change anything in Spring Framework implementation.
I looks to me that it would be better to improve error message for setConnectionTimeout
reflective call to not to confuse class users.
I found out that there are plenty of ways how to customize JDK HttpClient
.
The problem is only combination of calls of requestFactory
and connectTimeout
methods in the same time.
What do you think?
package cz.publicstaticvoidmain.reproduce;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.http.client.ClientHttpRequestFactoryBuilderCustomizer;
import org.springframework.boot.http.client.JdkClientHttpRequestFactoryBuilder;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Profile;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import java.net.http.HttpClient;
import java.time.Duration;
@SpringBootApplication
public class ReproduceApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ReproduceApplication.class);
// Do not start HTTP stack
app.setWebApplicationType(WebApplicationType.NONE);
// app.setAdditionalProfiles("my-requestFactory-connectTimeout-ERROR");
// app.setAdditionalProfiles("my-requestFactory-and-httpclient-builder");
// app.setAdditionalProfiles("my-connectTimeout-no-request-factory");
// app.setAdditionalProfiles("my-connectTimeout-customizer");
app.setAdditionalProfiles("my-requestFactory-connectTimeout-customizer-ERROR");
app.run(args);
}
}
@Profile("my-requestFactory-connectTimeout-ERROR")
@Component
class ReproduceComponent1 {
public ReproduceComponent1(
RestTemplateBuilder restTemplateBuilder
) {
restTemplateBuilder
.requestFactory(() -> new JdkClientHttpRequestFactory())
.connectTimeout(Duration.ofMinutes(5))
.build();
}
}
@Profile("my-requestFactory-and-httpclient-builder")
@Component
class ReproduceComponent2 {
public ReproduceComponent2(
RestTemplateBuilder restTemplateBuilder
) {
restTemplateBuilder
.requestFactory(() -> new JdkClientHttpRequestFactory(
HttpClient.newBuilder()
.connectTimeout(Duration.ofMinutes(5))
.build()))
.build();
}
}
@Profile("my-connectTimeout-no-request-factory")
@Component
class ReproduceComponent3 {
public ReproduceComponent3(
RestTemplateBuilder restTemplateBuilder
) {
restTemplateBuilder
.connectTimeout(Duration.ofMinutes(5))
.build();
}
}
@Profile("my-connectTimeout-customizer")
@Component
class ReproduceComponent4 {
public ReproduceComponent4(
RestTemplateBuilder restTemplateBuilder
) {
restTemplateBuilder
.connectTimeout(Duration.ofMinutes(5))
.build();
}
}
@Profile("my-connectTimeout-customizer")
@Component
class Customizer4 implements ClientHttpRequestFactoryBuilderCustomizer<JdkClientHttpRequestFactoryBuilder> {
@Override
public JdkClientHttpRequestFactoryBuilder customize(JdkClientHttpRequestFactoryBuilder builder) {
return builder.withHttpClientCustomizer(httpClientBuilder -> {
httpClientBuilder.connectTimeout(Duration.ofMinutes(10));
});
}
}
@Profile("my-requestFactory-connectTimeout-customizer-ERROR")
@Component
class ReproduceComponent5 {
public ReproduceComponent5(
RestTemplateBuilder restTemplateBuilder
) {
restTemplateBuilder
.requestFactory(() -> {
return new JdkClientHttpRequestFactory(HttpClient.newBuilder().build());
})
.connectTimeout(Duration.ofMinutes(5))
.build();
}
}
@Profile("my-requestFactory-connectTimeout-customizer-ERROR")
@Component
class Customizer5 implements ClientHttpRequestFactoryBuilderCustomizer<JdkClientHttpRequestFactoryBuilder> {
@Override
public JdkClientHttpRequestFactoryBuilder customize(JdkClientHttpRequestFactoryBuilder builder) {
return builder.withHttpClientCustomizer(httpClientBuilder -> {
httpClientBuilder.connectTimeout(Duration.ofMinutes(10));
});
}
}
Comment From: wilkinsona
I don't think we can do that. ReflectiveComponentsClientHttpRequestFactoryBuilder
is intended for configuring a factory without knowledge of it, hence it attempting to use well-known configuration methods. If I have understood your recommendation correctly, it would have to have knowledge of HttpClient and its builder to provide a different message.
Comment From: bedla
I am more pointing to exception message like "Unable to setConnectionTimeoute because underlying class