Spring Boot AutoConfigure provides auto-configuration capabilities that automatically configure Spring applications based on jar dependencies present on the classpath
—
Handle special situations and edge cases in Spring Boot AutoConfigure.
Two auto-configurations depend on each other's beans.
@AutoConfiguration
public class ServiceAConfiguration {
@Bean
@Lazy // Break circular dependency
public ServiceA serviceA(ServiceB serviceB) {
return new ServiceA(serviceB);
}
}
@AutoConfiguration
public class ServiceBConfiguration {
@Bean
public ServiceB serviceB(@Lazy ServiceA serviceA) {
return new ServiceB(serviceA);
}
}Multiple beans of the same type, need to select one.
@AutoConfiguration
public class DataSourceConfiguration {
@Bean
@Primary // Default choice
public DataSource primaryDataSource() {
return new HikariDataSource();
}
@Bean
@Qualifier("readonly")
public DataSource readOnlyDataSource() {
return new HikariDataSource();
}
}
// Usage
@Service
public class MyService {
private final DataSource dataSource;
public MyService(@Qualifier("readonly") DataSource dataSource) {
this.dataSource = dataSource;
}
}Enable feature only when property is NOT set.
@Configuration
public class DefaultConfiguration {
@Bean
@ConditionalOnProperty(
name = "custom.service.url",
matchIfMissing = true // Match when property is missing
)
public ServiceClient defaultServiceClient() {
return new ServiceClient("http://default-url");
}
@Bean
@ConditionalOnProperty(name = "custom.service.url")
public ServiceClient customServiceClient(
@Value("${custom.service.url}") String url) {
return new ServiceClient(url);
}
}Apply configuration if ANY condition matches.
@Configuration
@Conditional(DatabaseOrCacheCondition.class)
public class StorageConfiguration {
// Configuration
}
static class DatabaseOrCacheCondition extends AnyNestedCondition {
DatabaseOrCacheCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty("database.enabled")
static class DatabaseEnabled {}
@ConditionalOnProperty("cache.enabled")
static class CacheEnabled {}
}Register beans based on runtime configuration.
@AutoConfiguration
public class DynamicBeanConfiguration implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// Read configuration
Environment env = ((EnvironmentCapable) registry).getEnvironment();
String[] serviceNames = env.getProperty(
"services.names",
String[].class,
new String[0]
);
// Register bean for each service
for (String name : serviceNames) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(ServiceClient.class)
.addConstructorArgValue(name)
.addConstructorArgValue(
env.getProperty("services." + name + ".url")
);
registry.registerBeanDefinition(
name + "Client",
builder.getBeanDefinition()
);
}
}
}Create bean only if another bean's method returns true.
// Don't do this - causes early initialization
@ConditionalOnExpression("@featureService.isEnabled('myFeature')")
// Instead, use property
@ConditionalOnProperty("features.myFeature.enabled")
// Or custom condition
public class FeatureEnabledCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(
ConditionContext context,
AnnotatedTypeMetadata metadata) {
String enabled = context.getEnvironment()
.getProperty("features.myFeature.enabled");
return "true".equals(enabled)
? ConditionOutcome.match()
: ConditionOutcome.noMatch("Feature disabled");
}
}Enable if ANY of multiple classes is present.
public class OnAnyDatabaseDriverCondition extends AnyNestedCondition {
OnAnyDatabaseDriverCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
static class MySQLPresent {}
@ConditionalOnClass(name = "org.postgresql.Driver")
static class PostgreSQLPresent {}
@ConditionalOnClass(name = "oracle.jdbc.OracleDriver")
static class OraclePresent {}
}
@Configuration
@Conditional(OnAnyDatabaseDriverCondition.class)
public class DatabaseConfiguration {
// Configuration
}Detect generic beans like Repository<User>.
@Configuration
public class RepositoryConfiguration {
@Bean
@ConditionalOnBean(
value = User.class,
parameterizedContainer = Repository.class
)
public UserService userService(Repository<User> userRepository) {
return new UserService(userRepository);
}
}Enable if any file matching pattern exists.
@Configuration
@ConditionalOnResource(resources = "classpath*:config/*.xml")
public class XmlBasedConfiguration {
@Bean
public XmlConfigLoader xmlConfigLoader(ResourceLoader loader) throws IOException {
Resource[] resources = loader.getResources("classpath*:config/*.xml");
return new XmlConfigLoader(resources);
}
}Different configuration for each cloud provider.
@Configuration
public class CloudConfiguration {
@Bean
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
public ServiceDiscovery k8sServiceDiscovery() {
return new KubernetesServiceDiscovery();
}
@Bean
@ConditionalOnCloudPlatform(CloudPlatform.CLOUD_FOUNDRY)
public ServiceDiscovery cfServiceDiscovery() {
return new CloudFoundryServiceDiscovery();
}
@Bean
@ConditionalOnCloudPlatform(CloudPlatform.NONE)
public ServiceDiscovery localServiceDiscovery() {
return new LocalServiceDiscovery();
}
}Different configuration for different Java versions.
@Configuration
public class JavaVersionConfiguration {
@Bean
@ConditionalOnJava(JavaVersion.TWENTY_ONE)
public Executor virtualThreadExecutor() {
return Executors.newVirtualThreadPerTaskExecutor();
}
@Bean
@ConditionalOnJava(
value = JavaVersion.TWENTY_ONE,
range = Range.OLDER_THAN
)
public Executor platformThreadExecutor() {
return Executors.newFixedThreadPool(10);
}
}Different configuration for WAR vs embedded server.
@Configuration
public class DeploymentConfiguration {
@Bean
@ConditionalOnWarDeployment
public ServletContextInitializer warInitializer() {
return servletContext -> {
// WAR-specific initialization
};
}
@Bean
@ConditionalOnNotWarDeployment
public WebServerFactoryCustomizer<TomcatServletWebServerFactory>
embeddedCustomizer() {
return factory -> {
// Embedded server customization
};
}
}Configure based on virtual vs platform threads.
@Configuration
public class ThreadingConfiguration {
@Bean
@ConditionalOnThreading(Threading.VIRTUAL)
public TaskExecutor virtualThreadTaskExecutor() {
return new VirtualThreadTaskExecutor();
}
@Bean
@ConditionalOnThreading(Threading.PLATFORM)
public TaskExecutor platformThreadTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.initialize();
return executor;
}
}Register filter only if another filter doesn't exist.
@Configuration
public class FilterConfiguration {
@Bean
@ConditionalOnMissingFilterBean(CorsFilter.class)
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> bean =
new FilterRegistrationBean<>();
bean.setFilter(new CorsFilter());
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}Require exactly one bean (or one primary).
@Configuration
public class SingleDataSourceConfiguration {
@Bean
@ConditionalOnSingleCandidate(DataSource.class)
public DataSourceHealthIndicator healthIndicator(DataSource dataSource) {
return new DataSourceHealthIndicator(dataSource);
}
}debug=true
logging.level.org.springframework.boot.autoconfigure=DEBUG@Component
public class ConditionReportPrinter implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ConditionEvaluationReport report = ConditionEvaluationReport.get(
event.getApplicationContext().getBeanFactory()
);
report.getConditionAndOutcomesBySource().forEach((source, outcomes) -> {
System.out.println(source + ": " + outcomes.isFullMatch());
});
}
}@ConditionalOnExpression