Hire Me
← All Writing Spring Boot

Building a Custom Auto-Configuration for Shared Libraries

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.

What auto-configuration is

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.

Project structure

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

Properties binding

@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
}

The auto-configuration class

@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.

Registering the auto-configuration

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

Ordering auto-configurations

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.

Adding multiple beans with conditions

@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.

Testing the auto-configuration

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.

Configuration metadata

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.

Samuel Jackson

Samuel Jackson

Senior Java Back End Developer & Contractor

Senior Java Back End Developer — Betfair Exchange API specialist, Spring Boot, AWS, and event-driven architecture. 20+ years delivering high-performance systems across betting, finance, energy, retail, and government. Available for Java contracting.