Explain how the ConditionEvaluationReport is populated during context startup and why condition ordering affects what the report shows.
answer
- ConfigurationClassPostProcessor → ConditionEvaluator.shouldSkip
- SpringBootCondition.matches records ConditionOutcome into report
- recordConditionEvaluation keyed by source
- OnBeanCondition reads current registry → order-sensitive
- ConfigurationPhase REGISTER_BEAN delays bean conditions
basics
~20 sAs Spring processes configuration classes, each @Conditional is evaluated by a ConditionEvaluator; the outcome is recorded into the ConditionEvaluationReport keyed by source. Because bean conditions (@ConditionalOnBean/OnMissingBean) only see beans registered so far, the order configs are processed changes the outcomes — and thus what the report records.
solid answer
~40 sDuring ConfigurationClassPostProcessor's parsing, Spring's ConditionEvaluator runs each condition; Spring Boot's SpringBootCondition subclasses (OnClassCondition, OnBeanCondition, OnPropertyCondition) return a ConditionOutcome, and SpringBootCondition records it into the ConditionEvaluationReport via recordConditionEvaluation(source, condition, outcome). The report is a bean in the factory; the logging listener/Actuator just render it. Class-level conditions are evaluated when the config class is considered; @Bean-method conditions when that method is processed. The ordering matters most for bean-presence conditions: @ConditionalOnMissingBean/@ConditionalOnBean inspect the current BeanDefinitionRegistry, which reflects only definitions registered up to that point. Auto-config ordering (@AutoConfiguration before/after, @AutoConfigureBefore/After, @AutoConfigureOrder) and the fact that user config is generally parsed first determine that state. So the same beans with different ordering can yield different Positive/Negative classifications — the report is a snapshot of an order-dependent process, not a pure function of the final context.
code
java · 29 lines// Simplified shape of what SpringBootCondition does:
//
// public abstract class SpringBootCondition implements Condition {
// public final boolean matches(ConditionContext context,
// AnnotatedTypeMetadata metadata) {
// String source = getName(metadata);
// ConditionOutcome outcome = getMatchOutcome(context, metadata); // subclass
// recordEvaluation(context, source, outcome); // -> ConditionEvaluationReport
// return outcome.isMatch();
// }
// public abstract ConditionOutcome getMatchOutcome(...);
// }
// OnBeanCondition declares REGISTER_BEAN phase so it evaluates after more
// bean definitions exist, reducing ordering surprises:
// class OnBeanCondition extends FilteringSpringBootCondition
// implements ConfigurationCondition {
// @Override public ConfigurationPhase getConfigurationPhase() {
// return ConfigurationPhase.REGISTER_BEAN;
// }
// }
// Ordering control on an auto-config you author:
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
public class MyRepositoryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
MyRepository myRepository(DataSource ds) { return new MyRepository(ds); }
}go deeper
Not expected; just know decisions are recorded during startup.
Understand conditions run before beans are instantiated and outcomes are recorded.
Explain SpringBootCondition recording outcomes and that bean conditions read current registry state.
Reason about ConfigurationPhase, auto-config ordering annotations, the import-filter optimization, and design conditions/ordering in libraries you author.
## Where it happens in the lifecycle Condition evaluation is part of **configuration class parsing**, driven by `ConfigurationClassPostProcessor` (a `BeanDefinitionRegistryPostProcessor`) during `refresh()`, well before bean instantiation. The engine is `org.springframework.context.annotation.ConditionEvaluator.shouldSkip(...)`, which for each `@Conditional`-declared `Condition` calls `condition.matches(context, metadata)`. ## Spring Boot's layer on top Spring Boot's conditions extend `SpringBootCondition`, whose `matches(...)` is a template method: it calls the concrete `getMatchOutcome(...)` (implemented by `OnClassCondition`, `OnBeanCondition`, `OnPropertyCondition`, `OnWebApplicationCondition`, `OnResourceCondition`, `OnExpressionCondition`, etc.), which returns a **`ConditionOutcome`** (a match flag + a human message). `SpringBootCondition.matches` then calls `ConditionEvaluationReport.get(beanFactory).recordConditionEvaluation(classOrMethodName, condition, outcome)`. That's how every decision lands in the report, keyed by **source** (the config class name, or `Class#beanMethod`). ## The report object `ConditionEvaluationReport` accumulates: - `conditionAndOutcomes` map: source → `ConditionAndOutcomes` (each with the `Condition` and its `ConditionOutcome`; `isFullMatch()` = all matched). - `exclusions`: recorded via `recordExclusions(...)` when auto-configs are filtered out (`spring.autoconfigure.exclude`, annotation `exclude`). - `unconditionalClasses`: auto-config candidates that had no conditions (`recordEvaluationCandidates` minus those with outcomes). It also chains to a parent report for context hierarchies. ## Auto-configuration filtering optimization Before full evaluation, Boot applies **`AutoConfigurationImportFilter`** implementations (`OnClassCondition`, `OnBeanCondition`, `OnWebApplicationCondition` also act as fast filters) to prune obviously-inapplicable candidates cheaply using `META-INF/spring-autoconfigure-metadata.properties` (generated at build time). Pruned classes still show as Negative matches, but this makes startup fast by avoiding loading their bytecode. ## Why ordering changes outcomes Bean-presence conditions read the **current** `ConfigurableListableBeanFactory` / registry state: - `OnBeanCondition` (`@ConditionalOnBean`, `@ConditionalOnMissingBean`, `@ConditionalOnSingleCandidate`) checks bean **definitions/types registered so far**. - That 'so far' is governed by processing order: user `@Configuration` is generally parsed before imported auto-configs; among auto-configs, order comes from `@AutoConfiguration(before=, after=)`, legacy `@AutoConfigureBefore`/`@AutoConfigureAfter`, and `@AutoConfigureOrder`. Consequently, if config A's `@ConditionalOnMissingBean` runs before config B registers that bean, A matches (creates the bean); reorder them and A backs off. The report faithfully records whichever happened. This is why Spring Boot documents that `@ConditionalOnBean`/`@ConditionalOnMissingBean` should only be used on **auto-configuration** classes (whose relative order is controlled), not on regular user config where ordering is unpredictable. ## Practical consequences for principals - The report is a **snapshot of an ordered process**; two logically-equivalent setups can classify differently. Don't treat Positive/Negative as a pure function of final bean set. - Fixing a duplicate/missing bean often means fixing **ordering** (place override in early user config, or annotate an auto-config with the right before/after), not changing the condition. - For libraries you author, put presence-based conditions on `@AutoConfiguration` classes and declare explicit ordering; verify with `ApplicationContextRunner` across both orderings. - The Actuator `conditions` endpoint and `--debug` render the same underlying object, so ops and dev see identical data. ## Edge cases - Nested `@Configuration` and `@Import` affect when a source is parsed and thus when its conditions fire. - Conditions can implement `ConfigurationCondition` and declare a `ConfigurationPhase` (`PARSE_CONFIGURATION` vs `REGISTER_BEAN`) to control whether they evaluate early (during parsing) or later (after bean definitions exist) — `OnBeanCondition` uses `REGISTER_BEAN` precisely so more bean definitions are visible, mitigating (but not eliminating) ordering surprises.
- Why does Spring Boot warn against putting @ConditionalOnBean/@ConditionalOnMissingBean on regular user @Configuration classes?Because those conditions inspect the beans registered so far, and the processing order of user config relative to everything else isn't guaranteed. Only auto-configuration classes have controlled ordering (@AutoConfigureBefore/After), so presence-conditions there behave predictably.
- What is the role of ConfigurationPhase.REGISTER_BEAN for OnBeanCondition?It tells Spring to evaluate the condition in the later register-bean phase rather than during config parsing, so more bean definitions have been registered and are visible. It reduces (but doesn't remove) ordering-dependent false negatives for bean-presence checks.
saying these in an interview costs you the question
- Believing the report is a pure function of the final bean set rather than an ordered evaluation
- Thinking @ConditionalOnMissingBean works reliably on arbitrary user @Configuration
- Assuming conditions are evaluated after all beans are instantiated (they run during config parsing / bean-definition registration)