You can create dynamic & configuration driven beans in Spring Boot in following way.

Note: Below example full code is available at https://github.com/imran9m/spring-boot-dynamic-beans

For the example, Let’s create dynamic RestClient beans with customized connection timeout and user-agent configuration to use anywhere we want.

  1. Let’s go with following configuration to manage customized properties for two dynamic RestClient beans.

application.yml

restClients:
  clients:
  - clientName: test1
    connectionTimeout: 6000
    responseTimeout: 6000
    userAgent: test1
  - clientName: test2
    connectionTimeout: 5000
    responseTimeout: 5000
    userAgent: test2
  1. Now, let’s load this configuration into custom configuration properties record.

DynamicRestBuilderProperties.java

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

@ConfigurationProperties(prefix = "rest-clients")
public record DynamicRestBuilderProperties(List<CustomClient> clients) {
    public record CustomClient(String clientName, int connectionTimeout, int responseTimeout, String userAgent) {
    }
}
  1. Let’s go ahead and create app config class to create custom and dynamic beans on @PostConstruct stage.
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestClient;

@Configuration
@EnableConfigurationProperties(DynamicRestBuilderProperties.class)
public class DemoConfig {

    private static final Logger logger = LoggerFactory.getLogger(DemoConfig.class);
    @Autowired
    private DynamicRestBuilderProperties dynamicRestBuilderProperties;

    @Autowired
    private ConfigurableApplicationContext configurableApplicationContext;

    @Autowired
    private RestClient.Builder restClientBuilder;

    public DemoConfig() {
        logger.info("!!!!! DemoConfig Initialized !!!!");
    }

    @PostConstruct
    public void init() {
        ConfigurableListableBeanFactory beanFactory = this.configurableApplicationContext.getBeanFactory();
        // iterate over properties and register new beans'
        for (DynamicRestBuilderProperties.CustomClient client : dynamicRestBuilderProperties.clients()) {
            RestClient tempClient = restClientBuilder
                                        .clone()
                                        .requestFactory(getClientHttpRequestFactory(client.connectionTimeout(), client.responseTimeout()))
                                        .defaultHeader("user-agent", client.userAgent())
                                        .build();
            beanFactory.autowireBean(tempClient);
            beanFactory.initializeBean(tempClient, client.clientName());
            beanFactory.registerSingleton(client.clientName(), tempClient);
            logger.info("{} bean created", client.clientName());
        }
    }

    private ClientHttpRequestFactory getClientHttpRequestFactory(int connectionTimeout, int responseTimeout) {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(responseTimeout);
        factory.setConnectTimeout(connectionTimeout);
        return factory;
    }
}
  1. Now, let’s create a sample controller to test these beans. By calling, https://httpbin.org/user-agent which returns our RestClient agent which we set during initialization.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClient;

@RestController
public class DemoController {
    private static final Logger logger = LoggerFactory.getLogger(DemoController.class);

    @Autowired
    @Qualifier("test1")
    private RestClient restClient;

    @Autowired
    @Qualifier("test2")
    private RestClient restClient2;

    public DemoController() {
    }

    @GetMapping("/test1")
    public ResponseEntity<String> test1() {
        return ResponseEntity.ok(restClient.get().uri("https://httpbin.org/user-agent").retrieve().body(String.class));
    }

    @GetMapping("/test2")
    public ResponseEntity<String> test2() {
        return ResponseEntity.ok(restClient2.get().uri("https://httpbin.org/user-agent").retrieve().body(String.class));
    }
}
  1. When you build(mvn clean install) the code and run it(java -jar .\target\spring-boot-dynamic-beans-0.0.1-SNAPSHOT.jar), we can see in bootup logs that beans are created.
PS C:\Users\Imran\Downloads\spring-boot-dynamic-beans> java -jar .\target\spring-boot-dynamic-beans-0.0.1-SNAPSHOT.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.4.1)

2025-01-03T23:40:09.019-05:00  INFO 17700 --- [democontroller] [           main] c.e.d.DemocontrollerApplication          : Starting DemocontrollerApplication v0.0.1-SNAPSHOT using Java 23.0.1 with PID 17700 (C:\Users\Imran\Downloads\spring-boot-dynamic-beans\target\spring-boot-dynamic-beans-0.0.1-SNAPSHOT.jar started by Imran in C:\Users\Imran\Downloads\spring-boot-dynamic-beans)
2025-01-03T23:40:09.022-05:00  INFO 17700 --- [democontroller] [           main] c.e.d.DemocontrollerApplication          : No active profile set, falling back to 1 default profile: "default"
2025-01-03T23:40:09.816-05:00  INFO 17700 --- [democontroller] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2025-01-03T23:40:09.828-05:00  INFO 17700 --- [democontroller] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-01-03T23:40:09.828-05:00  INFO 17700 --- [democontroller] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.34]
2025-01-03T23:40:09.852-05:00  INFO 17700 --- [democontroller] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-01-03T23:40:09.853-05:00  INFO 17700 --- [democontroller] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 776 ms
2025-01-03T23:40:09.887-05:00  INFO 17700 --- [democontroller] [           main] com.example.democontroller.DemoConfig    : !!!!! DemoConfig Initialized !!!!
2025-01-03T23:40:10.070-05:00  INFO 17700 --- [democontroller] [           main] com.example.democontroller.DemoConfig    : test1 bean created
2025-01-03T23:40:10.072-05:00  INFO 17700 --- [democontroller] [           main] com.example.democontroller.DemoConfig    : test2 bean created
2025-01-03T23:40:10.319-05:00  INFO 17700 --- [democontroller] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2025-01-03T23:40:10.332-05:00  INFO 17700 --- [democontroller] [           main] c.e.d.DemocontrollerApplication          : Started DemocontrollerApplication in 1.712 seconds (process running for 2.108)
  1. Finally, when we test with following Urls, we should see corresponding responses.
GET http://localhost:8080/test1

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 28
Date: Sat, 04 Jan 2025 04:35:58 GMT
Connection: close

{
  "user-agent": "test1"
}
GET http://localhost:8080/test2

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 28
Date: Sat, 04 Jan 2025 04:33:33 GMT
Connection: close

{
  "user-agent": "test2"
}