Dynamic Bean Registration in Spring Boot: Using Extension Points and Conditional Annotations

This guide explains multiple ways to dynamically register beans in Spring Boot—including @ConditionalOnProperty, ImportBeanDefinitionRegistrar, the new Spring 7 BeanRegistrar API, and runtime registration via @PostConstruct—while outlining configuration details, lifecycle considerations, and best‑practice recommendations for flexible, condition‑driven bean management.

Senior Xiao Ying
Senior Xiao Ying
Senior Xiao Ying
Dynamic Bean Registration in Spring Boot: Using Extension Points and Conditional Annotations

Dynamic Bean Registration in Spring Boot

1. Methods

Spring Boot provides several mechanisms for dynamic bean registration. Their core characteristics are compared in the diagram below.

1.1 @ConditionalOnProperty

This is the simplest conditional registration method. Add the @ConditionalOnProperty annotation to a configuration class or @Bean method.

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConnectionFactoryConfig {

    @Bean
    @ConditionalOnProperty(name = "connection.type", havingValue = "caching", matchIfMissing = true)
    public CachingConnectionFactory cachingConnectionFactory() {
        return new CachingConnectionFactory();
    }

    @Bean
    @ConditionalOnProperty(name = "connection.type", havingValue = "userCredentials")
    public UserCredentialsConnectionFactoryAdapter userCredentialsConnectionFactoryAdapter() {
        return new UserCredentialsConnectionFactoryAdapter();
    }
}

application.properties configuration:

# Enable CachingConnectionFactory
connection.type=caching
# Or enable UserCredentialsConnectionFactoryAdapter
# connection.type=userCredentials

Annotation attribute details: name (required): the property name to check. havingValue (optional): the value to match; defaults to "true". matchIfMissing (optional): whether to match when the property is absent; default is false. Set to true to define default behavior.

1.2 ImportBeanDefinitionRegistrar

Use this when complex logic or custom annotations are required.

Step 1: Define a custom annotation

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyCustomBeanRegistrar.class)
public @interface EnableCustomService {
    String[] basePackages() default {};
}

Step 2: Implement the registrar

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MyCustomBeanRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 1. Retrieve annotation attributes
        var attributes = importingClassMetadata.getAnnotationAttributes(EnableCustomService.class.getName());
        var basePackages = (String[]) attributes.get("basePackages");

        // 2. Build bean definition dynamically
        var builder = BeanDefinitionBuilder.genericBeanDefinition(MyDynamicService.class);
        builder.addPropertyValue("message", "Dynamically registered!");

        // 3. Get definition and set scope
        var beanDefinition = builder.getBeanDefinition();
        beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);

        // 4. Register bean
        registry.registerBeanDefinition("myDynamicService", beanDefinition);
        // Additional scanning of packages can be added here
    }
}

Step 3: Apply the custom annotation on the application class

@SpringBootApplication
@EnableCustomService(basePackages = "com.example.dynamic")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

1.3 Spring 7 BeanRegistrar

Spring 7 introduces the BeanRegistrar interface, offering a concise, declarative, and AOT‑compatible registration approach.

Example implementation

// 1. Implement BeanRegistrar
import org.springframework.boot.BeanRegistrar;
import org.springframework.boot.BeanRegistry;

public class MyModernBeanRegistrar implements BeanRegistrar {
    @Override
    public void register(BeanRegistry registry, Environment environment) {
        // Fluent API registration
        registry.registerBean(MyModernService.class, spec -> spec
            .singleton()   // singleton scope
            .lazyInit(true) // lazy initialization
            .primary(true) // primary candidate
        );
    }
}

Configuration to import the registrar:

@Configuration
@Import(MyModernBeanRegistrar.class) // or MyKotlinBeanRegistrar.class
public class ModernConfig {
}

1.4 Runtime registration after context refresh

Beans can also be registered manually after the application starts, for example inside a @PostConstruct method.

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

@Component
public class RuntimeBeanRegistrar {
    private final BeanDefinitionRegistry registry;

    public RuntimeBeanRegistrar(BeanDefinitionRegistry registry) {
        this.registry = registry;
    }

    @PostConstruct
    public void registerRuntimeBeans() {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(RuntimeService.class);
        beanDefinition.setScope(GenericBeanDefinition.SCOPE_SINGLETON);
        // Register into the container
        registry.registerBeanDefinition("runtimeService", beanDefinition);
    }
}

Beans registered after the BeanFactoryPostProcessor phase may not be processed by certain BeanPostProcessor s such as AOP or transaction management, so use with caution.

Core Points

Choose the method based on requirement complexity : simple configuration‑driven conditions favor @ConditionalOnProperty, while complex logic may need ImportBeanDefinitionRegistrar or the new BeanRegistrar.

Leverage Spring 7 features : for new projects, especially those targeting GraalVM native images, prefer the BeanRegistrar API.

Understand bean lifecycle impacts : the registration timing (before or after context refresh) influences lifecycle management and compatibility with AOP, transaction proxies, etc.

Use @Bean(defaultCandidate = false) when multiple implementations exist to guide autowiring decisions.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpring BootImportBeanDefinitionRegistrarDynamic Bean RegistrationBeanRegistrarConditionalOnProperty
Senior Xiao Ying
Written by

Senior Xiao Ying

Dedicated to sharing Java backend technical experience and original tutorials, offering career transition advice and resume editing. Recognized as a rising star in CSDN's Java backend community and ranked Top 3 in the 2022 New Star Program for Java backend.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.