Spring Boot AutoConfigure provides auto-configuration capabilities that automatically configure Spring applications based on jar dependencies present on the classpath
—
Learn how to create your own auto-configuration classes that integrate seamlessly with Spring Boot.
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnClass(MyService.class)
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService() {
return new MyService();
}
}Create META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.example.MyServiceAutoConfigurationUsers can now add your JAR to their classpath and get automatic configuration.
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "myservice")
public class MyServiceProperties {
private boolean enabled = true;
private int timeout = 30;
private String endpoint;
// Getters and setters
}@AutoConfiguration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "myservice", name = "enabled", havingValue = "true", matchIfMissing = true)
public MyService myService(MyServiceProperties properties) {
return new MyService(
properties.getEndpoint(),
properties.getTimeout()
);
}
}myservice.enabled=true
myservice.timeout=60
myservice.endpoint=https://api.example.com@AutoConfiguration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MyDatabaseAutoConfiguration {
@Bean
public DatabaseInitializer initializer(DataSource dataSource) {
return new DatabaseInitializer(dataSource);
}
}@AutoConfiguration
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
public class MyWebAutoConfiguration {
// Runs before web configuration
}@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class HighPriorityAutoConfiguration {
// Runs early in the configuration process
}@AutoConfiguration
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
@ConditionalOnProperty(prefix = "app.database", name = "enabled")
@ConditionalOnMissingBean(DataSource.class)
public class DatabaseAutoConfiguration {
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
}public class OnDatabaseTypeCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(
ConditionContext context,
AnnotatedTypeMetadata metadata) {
String dbType = context.getEnvironment()
.getProperty("database.type");
if ("mysql".equals(dbType)) {
return ConditionOutcome.match("MySQL database configured");
}
return ConditionOutcome.noMatch("MySQL not configured");
}
}
@Configuration
@Conditional(OnDatabaseTypeCondition.class)
public class MySQLConfiguration {
// MySQL-specific configuration
}my-autoconfigure/
├── src/main/java/
│ └── com/example/
│ ├── MyServiceAutoConfiguration.java
│ ├── MyServiceProperties.java
│ └── MyService.java
└── src/main/resources/
└── META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.importsCreate a separate starter module:
<project>
<artifactId>my-spring-boot-starter</artifactId>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-autoconfigure</artifactId>
</dependency>
<!-- Other required dependencies -->
</dependencies>
</project>import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class MyServiceAutoConfigurationTest {
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(
MyServiceAutoConfiguration.class
));
@Test
void serviceIsAutoConfigured() {
contextRunner.run(context -> {
assertThat(context).hasSingleBean(MyService.class);
});
}
@Test
void serviceNotConfiguredWhenDisabled() {
contextRunner
.withPropertyValues("myservice.enabled=false")
.run(context -> {
assertThat(context).doesNotHaveBean(MyService.class);
});
}
@Test
void userConfigurationTakesPrecedence() {
contextRunner
.withUserConfiguration(UserConfiguration.class)
.run(context -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class))
.isInstanceOf(CustomMyService.class);
});
}
@Configuration
static class UserConfiguration {
@Bean
public MyService myService() {
return new CustomMyService();
}
}
}@Test
void autoConfigurationWithDataSource() {
contextRunner
.withConfiguration(AutoConfigurations.of(
DataSourceAutoConfiguration.class,
MyDatabaseAutoConfiguration.class
))
.withPropertyValues(
"spring.datasource.url=jdbc:h2:mem:test"
)
.run(context -> {
assertThat(context).hasSingleBean(DataSource.class);
assertThat(context).hasSingleBean(DatabaseInitializer.class);
});
}@ConfigurationProperties(prefix = "myservice")
public class MyServiceProperties {
private boolean enabled = true; // Enabled by default
private int timeout = 30; // Reasonable default
private int retries = 3; // Safe default
}@Bean
@ConditionalOnMissingBean // User can provide their own
public MyService myService() {
return new MyService();
}@Bean
public MyService myService(MyServiceProperties properties) {
if (properties.getEndpoint() == null) {
throw new IllegalStateException(
"myservice.endpoint must be configured"
);
}
return new MyService(properties.getEndpoint());
}/**
* Auto-configuration for MyService.
*
* <p>Activated when {@link MyService} is on the classpath.
* Can be disabled by setting {@code myservice.enabled=false}.
*
* <p>Configuration properties:
* <ul>
* <li>{@code myservice.enabled} - Enable/disable the service (default: true)</li>
* <li>{@code myservice.endpoint} - Service endpoint URL (required)</li>
* <li>{@code myservice.timeout} - Request timeout in seconds (default: 30)</li>
* </ul>
*/
@AutoConfiguration
public class MyServiceAutoConfiguration {
// ...
}// Good: Type-safe and fast
@ConditionalOnClass(MyService.class)
@ConditionalOnProperty(prefix = "myservice", name = "enabled")
// Avoid: Slower and error-prone
@ConditionalOnExpression("${myservice.enabled:false}")@AutoConfiguration
public class CacheAutoConfiguration {
@Bean
@ConditionalOnClass(name = "com.github.benmanes.caffeine.cache.Caffeine")
@ConditionalOnMissingBean
public CacheManager caffeineCacheManager() {
return new CaffeineCacheManager();
}
@Bean
@ConditionalOnClass(RedisConnectionFactory.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean
public CacheManager redisCacheManager(RedisConnectionFactory factory) {
return RedisCacheManager.builder(factory).build();
}
@Bean
@ConditionalOnMissingBean
public CacheManager simpleCacheManager() {
return new ConcurrentMapCacheManager();
}
}@AutoConfiguration
@Import({
MyServiceConfiguration.class,
MyServiceHealthIndicatorConfiguration.class
})
public class MyServiceAutoConfiguration {
// Main configuration
}@AutoConfiguration
public class DynamicAutoConfiguration implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(
AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// Dynamically register beans based on environment
if (shouldRegisterBean()) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(MyService.class);
registry.registerBeanDefinition("myService",
builder.getBeanDefinition());
}
}
}// Problem: Circular dependency
@Bean
public ServiceA serviceA(ServiceB serviceB) { }
@Bean
public ServiceB serviceB(ServiceA serviceA) { }
// Solution: Use @Lazy
@Bean
public ServiceA serviceA(@Lazy ServiceB serviceB) { }// Problem: Causes early initialization
@ConditionalOnExpression("@myBean.isReady()")
// Solution: Use properties
@ConditionalOnProperty("mybean.ready")// Problem: Always creates bean
@Bean
public MyService myService() { }
// Solution: Add conditions
@Bean
@ConditionalOnClass(MyService.class)
@ConditionalOnMissingBean
public MyService myService() { }