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.
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=userCredentialsAnnotation 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
