skip to content

Explain how the ConditionEvaluationReport is populated during context startup and why condition ordering affects what the report shows.

level: principalimportance: nice to knowfreq 15%

answer

  1. ConfigurationClassPostProcessor → ConditionEvaluator.shouldSkip
  2. SpringBootCondition.matches records ConditionOutcome into report
  3. recordConditionEvaluation keyed by source
  4. OnBeanCondition reads current registry → order-sensitive
  5. ConfigurationPhase REGISTER_BEAN delays bean conditions

basics

~20 s

As 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 s

During 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
java
// 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

for a junior

Not expected; just know decisions are recorded during startup.

for a middle

Understand conditions run before beans are instantiated and outcomes are recorded.

for a senior

Explain SpringBootCondition recording outcomes and that bean conditions read current registry state.

for a principal

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)

context