skip to content

How do you author a custom auto-configuration/starter correctly in Spring Boot 3, including ordering and back-off?

level: principalimportance: nice to knowfreq 28%

answer

  1. @AutoConfiguration not @Configuration
  2. @ConditionalOnClass + @ConditionalOnMissingBean
  3. register FQN in ...AutoConfiguration.imports
  4. order via before/after or @AutoConfigureAfter
  5. test with ApplicationContextRunner

basics

~10 s

Annotate the class with @AutoConfiguration, guard beans with conditions like @ConditionalOnClass and @ConditionalOnMissingBean, register the class in META-INF/spring/...AutoConfiguration.imports, and control order with @AutoConfiguration(before/after) or @AutoConfigureBefore/After.

solid answer

~40 s

In Boot 3 a custom auto-configuration class must be annotated with @AutoConfiguration (a specialized @Configuration(proxyBeanMethods=false) that also carries ordering semantics), not plain @Configuration. Guard the class and each @Bean so it's a good citizen: @ConditionalOnClass so it activates only when the relevant library is present, and @ConditionalOnMissingBean on the beans so consumers can override them. Register the fully-qualified class name in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (never rely on component scanning — it's outside the app's package). Externalize settings with an @ConfigurationProperties type. Control ordering relative to other auto-configs via @AutoConfiguration(before=..., after=...) or the standalone @AutoConfigureBefore/@AutoConfigureAfter/@AutoConfigureOrder. Split into a two-module 'starter' (thin dependency aggregator) plus an 'autoconfigure' module, following Boot's own convention. Add the spring-boot-configuration-processor so metadata and IDE hints are generated.

code

java · 32 lines
java
// autoconfigure module
@ConfigurationProperties("payment")
public record PaymentProperties(String baseUrl, String apiKey) { }

@AutoConfiguration(after = DataSourceAutoConfiguration.class)
@ConditionalOnClass(PaymentClient.class)
@ConditionalOnProperty(prefix = "payment", name = "enabled", matchIfMissing = true)
@EnableConfigurationProperties(PaymentProperties.class)
public class PaymentAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    PaymentClient paymentClient(PaymentProperties props) {
        return new PaymentClient(props.baseUrl(), props.apiKey());
    }
}

// src/main/resources/META-INF/spring/
//   org.springframework.boot.autoconfigure.AutoConfiguration.imports
//     com.acme.payment.PaymentAutoConfiguration

// Test with ApplicationContextRunner
class PaymentAutoConfigurationTests {
    private final ApplicationContextRunner runner = new ApplicationContextRunner()
        .withConfiguration(AutoConfigurations.of(PaymentAutoConfiguration.class));

    @Test
    void backsOffWhenUserDefinesBean() {
        runner.withBean(PaymentClient.class, () -> mock(PaymentClient.class))
              .run(ctx -> assertThat(ctx).hasSingleBean(PaymentClient.class));
    }
}

go deeper

for a junior

Aware that starters bundle auto-config; unlikely to author one.

for a middle

Can register a class in the imports file and add basic conditions.

for a senior

Designs correct conditions, ordering, and @ConfigurationProperties; tests with ApplicationContextRunner.

for a principal

Owns starter module split, back-off contract, metadata generation, ordering cycles, and startup-cost implications across many consumers.

## Anatomy of a good starter Spring's own convention splits a starter into two modules: - **`acme-spring-boot-starter`** — an (almost) empty jar that just depends on the autoconfigure module plus the libraries a user needs. It's the artifact consumers add. - **`acme-spring-boot-autoconfigure`** — contains the `@AutoConfiguration` classes, `@ConfigurationProperties`, and the imports file. For small internal libs you can collapse both into one module, but the split keeps dependency graphs clean. ## The auto-configuration class Use `@AutoConfiguration`, introduced in Boot 2.7 and required for auto-config in 3.0. It is meta-annotated with `@Configuration(proxyBeanMethods = false)` (auto-configs don't need CGLIB proxying since they're internal) and provides `before`/`after`/`beforeName`/`afterName` attributes for ordering. ```java @AutoConfiguration(after = DataSourceAutoConfiguration.class) @ConditionalOnClass(PaymentClient.class) @EnableConfigurationProperties(PaymentProperties.class) public class PaymentAutoConfiguration { @Bean @ConditionalOnMissingBean PaymentClient paymentClient(PaymentProperties props) { return new PaymentClient(props.getBaseUrl(), props.getApiKey()); } } ``` ## Being a well-behaved auto-configuration - **`@ConditionalOnClass`** at the class level: activate only when the integration's key type is present, so the auto-config silently backs off when the library isn't used. - **`@ConditionalOnMissingBean`** on every `@Bean`: let application authors override any default by declaring their own bean. This is the contract that makes Boot overridable. - **`@ConditionalOnProperty`** to gate optional features behind a flag. - **`@ConfigurationProperties`** (registered via `@EnableConfigurationProperties`) to externalize all knobs into `application.yml` with type safety. ## Registration — the imports file Auto-config classes are **not** component-scanned (they live outside the app base package). Register each by FQN in: ``` src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ``` One class per line. Omitting this is the #1 reason a custom auto-config 'does nothing.' ## Ordering When your beans depend on another auto-config's beans (e.g., you need the `DataSource` first), express it: - `@AutoConfiguration(after = DataSourceAutoConfiguration.class)` — preferred, on the class. - `@AutoConfigureBefore` / `@AutoConfigureAfter` — standalone equivalents. - `@AutoConfigureOrder(int)` — coarse priority when before/after isn't expressive enough. Ordering only sequences **auto-configs relative to each other**; the whole auto-config phase still runs after user config. ## Metadata & IDE support Add `spring-boot-configuration-processor` (annotation processor). It generates: - `META-INF/spring-configuration-metadata.json` for IDE auto-completion of your properties. - Contributes to `spring-autoconfigure-metadata.properties`, which powers the fast `AutoConfigurationImportFilter` pre-pass so your candidate is filtered cheaply. ## Testing Use `ApplicationContextRunner` — Boot's test utility for exercising auto-configuration in isolation: ```java new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(PaymentAutoConfiguration.class)) .withPropertyValues("payment.api-key=abc") .run(ctx -> assertThat(ctx).hasSingleBean(PaymentClient.class)); ``` Assert it activates when expected, backs off when a user bean exists, and stays off when `@ConditionalOnClass` isn't satisfied. ## Gotchas / anti-patterns - Using plain `@Configuration` instead of `@AutoConfiguration` — ordering semantics and Boot's tooling won't treat it as an auto-config. - Forgetting the imports file — class is never discovered. - Missing `@ConditionalOnMissingBean` — you rob consumers of the ability to override, breaking the Boot contract. - Heavy work in the constructor/`@Bean` at definition time — auto-configs should be cheap to evaluate. - Relying on component scanning to pick it up — it won't, by design. - Circular ordering via before/after between two auto-configs — Boot will report an ordering cycle.

  • Why annotate with @AutoConfiguration instead of @Configuration?
    @AutoConfiguration is a @Configuration(proxyBeanMethods=false) specialization carrying auto-config ordering attributes (before/after) and is what Boot's tooling and the imports mechanism expect for auto-configurations in 3.0.
  • How do you ensure your auto-config runs after the DataSource is available?
    Use @AutoConfiguration(after = DataSourceAutoConfiguration.class) or @AutoConfigureAfter(DataSourceAutoConfiguration.class); ordering annotations sequence auto-configs relative to one another.
  • What tool tests an auto-configuration in isolation?
    ApplicationContextRunner (and WebApplicationContextRunner/ReactiveWebApplicationContextRunner), which let you assert activation, back-off, and property-driven behavior without a full app.

saying these in an interview costs you the question

  • Relying on @ComponentScan to discover the auto-config instead of the imports file.
  • Using plain @Configuration and expecting Boot ordering/tooling to treat it as an auto-config.
  • Omitting @ConditionalOnMissingBean, preventing consumers from overriding the default bean.
  • Assuming @AutoConfigureAfter can order your auto-config relative to user @Configuration (it orders auto-configs among themselves).

context