What is ConfigurationCondition and ConfigurationPhase, and why is the phase important for conditions like @ConditionalOnMissingBean?
answer
- ConfigurationCondition extends Condition + getConfigurationPhase()
- PARSE_CONFIGURATION (skip whole @Configuration) vs REGISTER_BEAN (after parsing)
- @ConditionalOnBean/OnMissingBean -> OnBeanCondition -> REGISTER_BEAN
- User config parsed before auto-config -> correct back-off
- Only sees definitions registered so far -> @AutoConfigureBefore/After
basics
~10 sConfigurationCondition extends Condition and adds getConfigurationPhase(), returning PARSE_CONFIGURATION or REGISTER_BEAN. It tells Spring when to evaluate the condition. 'Missing bean' checks use REGISTER_BEAN so they run after all bean definitions are known.
solid answer
~40 sA plain Condition is evaluated eagerly while configuration classes are parsed. That's fine for classpath or property checks, but wrong for conditions that inspect what other beans exist — at parse time the registry is incomplete, so a 'match only if no bean of type X' check could see nothing and match prematurely. ConfigurationCondition (a sub-interface of Condition) fixes this with getConfigurationPhase(): PARSE_CONFIGURATION means 'evaluate while parsing @Configuration classes' (affects whether the config class itself is processed); REGISTER_BEAN means 'evaluate later, when adding regular bean definitions, after all configuration classes have been parsed'. Boot's @ConditionalOnBean/@ConditionalOnMissingBean implement ConfigurationCondition and return REGISTER_BEAN so the registry is fully populated and back-off decisions are deterministic. Even so, ordering between auto-configurations still matters, which is why user config is parsed before auto-config.
code
java · 19 linespublic class OnMissingMetricsCondition implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
// defer until all @Configuration classes are parsed
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConfigurableListableBeanFactory bf = context.getBeanFactory();
return bf != null && bf.getBeanNamesForType(MetricsService.class, false, false).length == 0;
}
}
@Configuration
class MetricsAutoConfig {
@Bean
@Conditional(OnMissingMetricsCondition.class)
MetricsService defaultMetrics() { return new MetricsService(); }
}go deeper
Know there are two phases and that 'missing bean' checks must run late; details optional at this level.
State that ConfigurationCondition adds getConfigurationPhase() with PARSE_CONFIGURATION vs REGISTER_BEAN and give the missing-bean example.
Explain the incomplete-registry problem, why OnBeanCondition uses REGISTER_BEAN, and the user-before-auto-config parse order.
Add cross-auto-config ordering limits, @AutoConfigureBefore/After, return-type inference for type matching, and design trade-offs of back-off.
**Why two phases exist.** Conditions run inside `ConfigurationClassPostProcessor`, which does its work in stages: it first *parses* configuration classes (discovering `@Configuration`, `@Import`, nested config, `@Bean` methods) and then *registers* the resulting bean definitions. A plain `Condition` is checked eagerly during parsing. For conditions that only look at the classpath or the `Environment`, timing doesn't matter — the answer is the same whenever you ask. But for conditions that ask 'does a bean of type X already exist?', the answer depends on how much has been registered so far. Evaluated too early, the registry is incomplete and the condition gives a wrong, order-dependent answer. **ConfigurationCondition.** `org.springframework.context.annotation.ConfigurationCondition` extends `Condition` and adds: ```java ConfigurationPhase getConfigurationPhase(); ``` The nested enum `ConfigurationCondition.ConfigurationPhase` has two values: - **`PARSE_CONFIGURATION`** — the condition is evaluated while the `@Configuration` class is being parsed. If it doesn't match, the whole configuration class is skipped and never contributes any beans, imports, or nested configs. Use this when the condition should decide whether to even *consider* a configuration class. - **`REGISTER_BEAN`** — the condition is evaluated later, when bean definitions are actually being registered, *after* all configuration classes have been parsed. At this point the set of definitions is much more complete, so type-presence checks are meaningful. **How Boot uses it.** `@ConditionalOnBean` and `@ConditionalOnMissingBean` are backed by `OnBeanCondition`, which implements `ConfigurationCondition` and returns `REGISTER_BEAN`. That's why the documented rule is: these conditions only reliably see beans that were defined *before* the condition is evaluated — and Spring Boot deliberately parses **user configuration before auto-configuration**, so a user-defined bean causes the matching auto-config bean to back off. `@ConditionalOnClass`/`@ConditionalOnProperty` don't need this — they're phase-agnostic classpath/environment checks (though Boot still often evaluates them appropriately). **The key gotcha this solves.** If you naively write a plain `Condition` that does `beanFactory.getBeanNamesForType(Foo.class).length == 0` and place it on a `@Bean`, you get non-deterministic results: whether `Foo` has been registered yet depends on parse order. Implementing `ConfigurationCondition` with `REGISTER_BEAN` defers the check so it's stable. **Further ordering caveats.** - Even in `REGISTER_BEAN`, only definitions processed *so far* are visible. Across multiple auto-configurations you still need `@AutoConfigureBefore/After/Order` to guarantee relative ordering; conditions can't see beans from an auto-config that hasn't run yet. - `@ConditionalOnMissingBean` should live on auto-config beans, not user beans, precisely because of the user-before-auto-config parse order. - The `@Bean` method's declared return type is what `OnBeanCondition` matches on for type inference — an overly generic return type (e.g. `Object`) weakens the match. **When to choose which phase.** Gate an entire `@Configuration` (based on classpath/property) → `PARSE_CONFIGURATION` (or a plain Condition). Gate a bean on the presence/absence of *other beans* → you need `REGISTER_BEAN` via `ConfigurationCondition`.
- What goes wrong if a 'no bean of type X exists' condition is a plain Condition (PARSE_CONFIGURATION-timed)?It's evaluated during parsing when the registry is incomplete, so it may not see a bean that will be registered later and matches prematurely — non-deterministic, order-dependent behavior. REGISTER_BEAN defers it until definitions are populated.
- Does REGISTER_BEAN guarantee the condition sees every bean in the context?No — it only sees definitions registered up to that point. Across auto-configurations you still need @AutoConfigureBefore/After/Order, and Boot parses user config before auto-config so user beans win.
- Which phase should gate an entire @Configuration class on a classpath check?PARSE_CONFIGURATION (or just a plain Condition), so if it doesn't match the whole class — with its @Bean methods and @Imports — is skipped before parsing continues.
saying these in an interview costs you the question
- Claiming a plain Condition reliably sees all other beans — it runs at parse time with an incomplete registry
- Saying REGISTER_BEAN sees the fully built context — it only sees definitions processed so far
- Putting @ConditionalOnMissingBean on user beans and expecting correct back-off
- Thinking ConfigurationPhase controls runtime vs startup rather than parse-vs-register ordering