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 does not support it, see docs about customization" and then update docs about it and put the examples I found out. What do you think?