skip to content

What's the difference between registering a class via withConfiguration(AutoConfigurations.of(...)) versus withUserConfiguration(...) in a runner, and why does it matter for @ConditionalOnMissingBean?

level: principalimportance: should knowfreq 18%

answer

  1. AutoConfigurations.of = auto-config: ordered + processed last
  2. withUserConfiguration/withBean = user beans: processed first
  3. @ConditionalOnMissingBean is order-sensitive
  4. user bean must be seen first to trigger back-off
  5. @AutoConfigureAfter honored only inside AutoConfigurations.of

basics

~10 s

AutoConfigurations.of(...) registers classes as auto-configuration — processed last, in auto-config order, honoring @AutoConfigureAfter/Before. withUserConfiguration registers them as ordinary user beans, processed first. That ordering is exactly what @ConditionalOnMissingBean depends on.

solid answer

~40 s

withConfiguration(AutoConfigurations.of(...)) wraps classes in an AutoConfigurations set so the runner treats them as real auto-configuration: they're ordered by @AutoConfigureOrder/@AutoConfigureAfter/@AutoConfigureBefore and, crucially, evaluated after user configuration — mirroring how Spring Boot loads auto-configs at runtime. withUserConfiguration (and withBean) register classes as ordinary application config, processed before auto-configuration. This matters because @ConditionalOnMissingBean is order-sensitive: it only backs off if the competing bean is already defined when the condition is evaluated. To realistically test that a user's bean overrides your auto-config default, register the auto-config via AutoConfigurations.of(...) and the user's bean via withUserConfiguration — then the user bean is seen first and your @ConditionalOnMissingBean correctly backs off. If you wrongly register the auto-config with withUserConfiguration, ordering isn't guaranteed and @ConditionalOnMissingBean / @AutoConfigureAfter tests can give false results that won't match production.

code

java · 12 lines
java
// auto-config under test: registered as auto-config (runs LAST)
ApplicationContextRunner runner = new ApplicationContextRunner()
        .withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class));

@Test
void userDefinedBeanOverridesDefault() {
    runner.withUserConfiguration(UserConfig.class)   // user bean: runs FIRST
          .run(ctx -> assertThat(ctx).hasSingleBean(MyService.class));
}

// MyAutoConfiguration#myService is @ConditionalOnMissingBean(MyService.class);
// because it runs after UserConfig, it sees the user bean and backs off.

go deeper

for a junior

Likely just uses AutoConfigurations.of without knowing why.

for a middle

Knows to use AutoConfigurations.of for the class under test and withUserConfiguration for stand-in user beans.

for a senior

Explains that auto-config runs last and that's what makes @ConditionalOnMissingBean back off correctly.

for a principal

Reasons about ordering fidelity to production, @AutoConfigureAfter/Before between auto-configs, and how mis-registration yields false-pass/false-fail tests.

## Two ways to register classes The runner offers distinct registration paths with different *semantics*: - `withConfiguration(AutoConfigurations.of(A.class, B.class))` — registers A and B as **auto-configuration** classes. `AutoConfigurations` is a special `Configurations` subtype that Spring Boot orders and processes exactly like production auto-config: sorted by `@AutoConfigureOrder`, `@AutoConfigureBefore`, `@AutoConfigureAfter`, and applied **after** all user configuration. - `withUserConfiguration(A.class)` / `withBean(...)` — registers A as ordinary **user** configuration, processed **before** auto-configuration, with no auto-config ordering semantics. ## Why ordering is not cosmetic — @ConditionalOnMissingBean `@ConditionalOnMissingBean` evaluates against the beans *already defined at the moment the condition runs*. Bean definitions are processed in a deterministic order; auto-config runs last so a user-defined bean of the same type already exists and the auto-config's `@ConditionalOnMissingBean` backs off. This is the mechanism that lets your `application.properties`-style defaults be overridden by a user's explicit `@Bean`. If you register your auto-configuration through `withUserConfiguration`, it's no longer 'last'. It competes as a peer user bean with undefined relative ordering, so `@ConditionalOnMissingBean` may evaluate *before* the user's bean is registered — producing a duplicate bean, or the wrong winner. The test would then either pass when production fails, or fail when production is fine. Both are dangerous: your test no longer models reality. ## Canonical override test ```java private final ApplicationContextRunner runner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class)); @Test void userBeanWinsOverAutoConfigDefault() { runner.withUserConfiguration(UserConfig.class) // user bean: processed first .run(ctx -> { assertThat(ctx).hasSingleBean(MyService.class); // exactly one assertThat(ctx).getBean(MyService.class) .isSameAs(ctx.getBean(UserConfig.class).myService()); }); } static class UserConfig { @Bean MyService myService() { return new MyService("custom"); } } ``` Here `MyAutoConfiguration#myService` is annotated `@ConditionalOnMissingBean(MyService.class)`. Because the auto-config is registered via `AutoConfigurations.of`, it runs after `UserConfig`, sees the user's bean, and backs off — leaving exactly one bean (the user's). Swap to `withUserConfiguration(MyAutoConfiguration.class)` and this guarantee evaporates. ## @AutoConfigureAfter / ordering between auto-configs The same distinction governs inter-auto-config ordering. If `BAutoConfiguration` declares `@AutoConfigureAfter(AAutoConfiguration.class)` and you want to test that B sees A's beans, both must be registered inside one `AutoConfigurations.of(A.class, B.class)` so the ordering annotations are honored. Registered as user config, the ordering hints are ignored. ## Practical rule - Class-under-test that IS an auto-configuration → `withConfiguration(AutoConfigurations.of(...))`. - Simulated user/application beans → `withUserConfiguration(...)` or `withBean(...)`. Mixing them this way makes the runner faithfully reproduce Spring Boot's real processing order, which is the whole point of using it to test conditions. ## Subtlety: withBean and order `withBean(...)` also registers as user-level and is seen before auto-config — perfect for injecting a stand-in dependency the auto-config expects, or for simulating a user-provided bean that should trigger back-off, without writing a `@Configuration` class.

  • You registered your auto-config with withUserConfiguration and the @ConditionalOnMissingBean override test is flaky. Why?
    withUserConfiguration processes it as a peer user bean with no auto-config ordering, so the condition may run before the user's bean is registered. Register it via AutoConfigurations.of(...) so it's processed last, matching production.
  • How do you make @AutoConfigureAfter ordering between two auto-configs actually take effect in a runner test?
    Register both in a single AutoConfigurations.of(A.class, B.class); AutoConfigurations honors @AutoConfigureAfter/Before. Registering them as user config ignores those hints.

saying these in an interview costs you the question

  • Registering the auto-config-under-test with withUserConfiguration
  • Believing bean processing order doesn't affect @ConditionalOnMissingBean
  • Expecting @AutoConfigureAfter to work when classes are registered as user config
  • Thinking AutoConfigurations.of only changes registration, not ordering/timing

context