How to write a Spring Boot auto-configuration that activates based on conditions — covering @ConditionalOnClass, @ConditionalOnProperty, spring.factories, and the AutoConfiguration.imports pattern.
When you extract shared infrastructure into a library — a custom metrics client, an audit logger, a circuit breaker wrapper — every team that uses it should get it configured with sensible defaults without writing boilerplate. Spring Boot auto-configuration is the mechanism for this: declare what beans to create and under what conditions, and Spring Boot handles the rest.
Spring Boot’s auto-configuration mechanism scans for classes annotated with @AutoConfiguration (or @Configuration in older patterns) registered via spring.factories or AutoConfiguration.imports. These classes create beans conditionally — only if a class is on the classpath, a property is set, or a bean is absent.
Your @SpringBootApplication triggers this scan. Every spring-boot-autoconfigure dependency you add contributes auto-configurations — DataSourceAutoConfiguration, KafkaAutoConfiguration, and so on.
my-trading-lib/
├── src/main/java/com/trinitylogic/lib/
│ ├── BetfairClientAutoConfiguration.java
│ ├── BetfairClientProperties.java
│ └── BetfairClient.java
└── src/main/resources/META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
@ConfigurationProperties(prefix = "trading.betfair")
@Validated
public class BetfairClientProperties {
@NotBlank
private String appKey;
@NotBlank
private String username;
private int connectionTimeout = 5000;
private int maxRetries = 3;
// getters and setters
}
@AutoConfiguration
@ConditionalOnClass(BetfairClient.class)
@ConditionalOnProperty(prefix = "trading.betfair", name = "app-key")
@EnableConfigurationProperties(BetfairClientProperties.class)
public class BetfairClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public BetfairClient betfairClient(BetfairClientProperties props) {
return BetfairClient.builder()
.appKey(props.getAppKey())
.username(props.getUsername())
.connectionTimeout(Duration.ofMillis(props.getConnectionTimeout()))
.maxRetries(props.getMaxRetries())
.build();
}
}
@ConditionalOnClass(BetfairClient.class) — the auto-configuration only activates if BetfairClient is on the classpath. If the library is not a dependency, nothing happens.
@ConditionalOnProperty(...) — only activates if trading.betfair.app-key is set. A library with missing configuration should not fail silently — requiring a property ensures misconfiguration is caught at startup.
@ConditionalOnMissingBean — if the application already defines a BetfairClient bean, this auto-configuration’s bean is not created. Applications can always override library defaults by declaring their own bean.
For Spring Boot 2.7+ and 3.x, create:
src/main/resources/META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports
Contents:
com.trinitylogic.lib.BetfairClientAutoConfiguration
One fully-qualified class name per line.
For Spring Boot < 2.7, use META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.trinitylogic.lib.BetfairClientAutoConfiguration
If your auto-configuration depends on another being processed first:
@AutoConfiguration(after = SslAutoConfiguration.class)
@ConditionalOnClass(BetfairClient.class)
public class BetfairClientAutoConfiguration {
// ...
}
after ensures the SSL context is available when BetfairClient is constructed.
@AutoConfiguration
@ConditionalOnClass(MeterRegistry.class)
@EnableConfigurationProperties(BetfairClientProperties.class)
public class BetfairClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public BetfairClient betfairClient(BetfairClientProperties props) {
return BetfairClient.builder()
.appKey(props.getAppKey())
.build();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(MeterRegistry.class)
public BetfairClientMetrics betfairClientMetrics(
BetfairClient client, MeterRegistry registry) {
return new BetfairClientMetrics(client, registry);
}
}
The metrics bean is only created if both BetfairClient and MeterRegistry are present. Consumers that do not have Micrometer get the client without metrics — no errors, no missing beans.
Use ApplicationContextRunner to test your conditions without starting a full application:
class BetfairClientAutoConfigurationTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(BetfairClientAutoConfiguration.class));
@Test
void createsClientWhenPropertyIsSet() {
runner.withPropertyValues("trading.betfair.app-key=test-key",
"trading.betfair.username=user")
.run(context -> {
assertThat(context).hasSingleBean(BetfairClient.class);
});
}
@Test
void doesNotCreateClientWhenPropertyMissing() {
runner.run(context -> {
assertThat(context).doesNotHaveBean(BetfairClient.class);
});
}
@Test
void backOffWhenBeanAlreadyDefined() {
runner.withPropertyValues("trading.betfair.app-key=test-key",
"trading.betfair.username=user")
.withUserConfiguration(CustomBetfairClientConfig.class)
.run(context -> {
assertThat(context).hasSingleBean(BetfairClient.class);
assertThat(context).getBean(BetfairClient.class)
.isInstanceOf(CustomBetfairClient.class);
});
}
}
ApplicationContextRunner runs the full condition evaluation without starting a server. It is the correct tool for auto-configuration testing.
Generate IDE completion and documentation by adding the annotation processor:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
This generates META-INF/spring-configuration-metadata.json at compile time. Developers using your library in IntelliJ or VS Code get autocomplete on trading.betfair.* properties.
A well-designed auto-configuration is invisible to happy-path users — the bean appears, correctly configured, with no boilerplate. It is explicit to users who need to customise — conditions are easy to override. And it fails loudly when required configuration is missing.
If you’re building shared infrastructure libraries for Spring Boot and want a review, get in touch.