Spring Boot
Java
Bean Creation
Dependency Injection
Programming

How do I create beans programmatically in Spring Boot?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Spring Boot usually creates beans from annotations and configuration classes, but some applications need runtime-driven registration. Examples include plugin systems, tenant-specific services, and feature-flagged integrations. Programmatic registration is powerful when you need dynamic behavior, as long as registration happens at the correct phase of application startup.

When Programmatic Registration Makes Sense

Use programmatic registration when bean set is not fully known at compile time.

Common scenarios:

  • Load a provider implementation based on external configuration.
  • Register one bean per tenant from database metadata.
  • Enable optional adapters only when a license key is present.

If beans are static and predictable, regular @Configuration plus @Bean is simpler and easier to maintain.

Option 1: Register with GenericApplicationContext

For straightforward runtime registration, inject GenericApplicationContext and call registerBean.

java
1import org.springframework.boot.CommandLineRunner;
2import org.springframework.boot.SpringApplication;
3import org.springframework.boot.autoconfigure.SpringBootApplication;
4import org.springframework.context.support.GenericApplicationContext;
5
6@SpringBootApplication
7public class DemoApplication {
8
9    public static void main(String[] args) {
10        SpringApplication.run(DemoApplication.class, args);
11    }
12
13    @org.springframework.context.annotation.Bean
14    CommandLineRunner registerDynamic(GenericApplicationContext context) {
15        return args -> {
16            context.registerBean("dynamicGreetingService", GreetingService.class,
17                () -> new GreetingService("hello from runtime registration"));
18        };
19    }
20}
21
22class GreetingService {
23    private final String message;
24
25    GreetingService(String message) {
26        this.message = message;
27    }
28
29    String message() {
30        return message;
31    }
32}

This approach is easy to read, but it may be too late for beans that must exist before auto-wiring of other startup components.

Option 2: Early Registration with BeanDefinitionRegistryPostProcessor

If other beans depend on dynamic bean definitions during context refresh, register in a registry post-processor.

java
1import org.springframework.beans.BeansException;
2import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
4import org.springframework.beans.factory.support.RootBeanDefinition;
5import org.springframework.context.annotation.Configuration;
6
7@Configuration
8public class DynamicBeanRegistry implements BeanDefinitionRegistryPostProcessor {
9
10    @Override
11    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
12        RootBeanDefinition def = new RootBeanDefinition(PaymentGatewayClient.class);
13        def.getConstructorArgumentValues().addGenericArgumentValue("https://api.example.local");
14        registry.registerBeanDefinition("paymentGatewayClient", def);
15    }
16
17    @Override
18    public void postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory beanFactory) {
19    }
20}
21
22class PaymentGatewayClient {
23    private final String endpoint;
24
25    PaymentGatewayClient(String endpoint) {
26        this.endpoint = endpoint;
27    }
28
29    String endpoint() {
30        return endpoint;
31    }
32}

This guarantees bean definition is available early in lifecycle.

Option 3: Conditional Dynamic Registration

Many teams combine environment checks with registration. Keep conditions centralized so startup behavior is predictable.

java
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.context.support.GenericApplicationContext;
4
5@Configuration
6public class FeatureDrivenConfig {
7
8    @Bean
9    Runnable registerFeatureBean(GenericApplicationContext context) {
10        return () -> {
11            String enabled = System.getenv().getOrDefault("EXPORT_FEATURE", "false");
12            if ("true".equalsIgnoreCase(enabled)) {
13                context.registerBean("exportService", ExportService.class, ExportService::new);
14            }
15        };
16    }
17}
18
19class ExportService {
20}

In production code, replace raw environment reads with Environment or ConfigurationProperties for better testability.

Wiring and Retrieval Patterns

After registration, you can consume dynamic beans by name or type.

java
1import org.springframework.context.ApplicationContext;
2
3class DemoUsage {
4    void show(ApplicationContext context) {
5        GreetingService svc = context.getBean("dynamicGreetingService", GreetingService.class);
6        System.out.println(svc.message());
7    }
8}

Prefer typed retrieval where possible. Name-based access is useful for plugin-style registries but should be documented clearly.

Testing Programmatic Bean Registration

Context tests should verify both positive and negative conditions.

java
1import org.junit.jupiter.api.Test;
2import org.springframework.beans.factory.annotation.Autowired;
3import org.springframework.boot.test.context.SpringBootTest;
4import static org.assertj.core.api.Assertions.assertThat;
5
6@SpringBootTest
7class DynamicBeanTests {
8
9    @Autowired
10    ApplicationContext context;
11
12    @Test
13    void shouldContainDynamicBean() {
14        assertThat(context.containsBean("paymentGatewayClient")).isTrue();
15    }
16}

For condition-driven registration, add separate tests with different property sets to prevent regressions.

Common Pitfalls

  • Registering dynamic beans too late in startup. Fix by using registry post-processor when early availability is required.
  • Spreading condition logic across many classes. Fix by centralizing registration rules.
  • Using string bean names without conventions. Fix by defining clear naming strategy and ownership.
  • Ignoring lifecycle implications for dynamic beans. Fix by documenting scope, initialization, and destruction behavior.
  • Skipping tests for disabled conditions. Fix by adding negative-path context tests.

Summary

  • Programmatic bean registration is useful for runtime-driven application composition.
  • 'GenericApplicationContext is simple for direct registration.'
  • Registry post-processors are better for early dependency-sensitive registration.
  • Keep condition logic explicit, deterministic, and test-covered.
  • Treat bean names and lifecycle as contracts, not ad hoc implementation details.

Course illustration
Course illustration

All Rights Reserved.