skip to content

How can you access the ConditionEvaluationReport programmatically, e.g. in a test, instead of reading logs?

level: seniorimportance: should knowfreq 30%

answer

  1. ConditionEvaluationReport.get(beanFactory)
  2. getConditionAndOutcomesBySource / getExclusions
  3. ApplicationContextRunner + AutoConfigurations.of
  4. FilteredClassLoader to fake a missing class
  5. Actuator /conditions endpoint

basics

~10 s

The report is itself a bean. Call ConditionEvaluationReport.get(beanFactory) to obtain it, then inspect getConditionAndOutcomesBySource(). In tests, ApplicationContextRunner exposes it via assertThat(context).getBean(...) or by inspecting the runner's context so you can assert on matches.

solid answer

~30 s

ConditionEvaluationReport is stored in the bean factory; retrieve it with the static ConditionEvaluationReport.get(ConfigurableListableBeanFactory). It exposes getConditionAndOutcomesBySource() (a map keyed by source class → conditions and their ConditionOutcome), getExclusions(), and getUnconditionalClasses(). This lets you build custom diagnostics or assert on decisions in tests. The idiomatic test tool is ApplicationContextRunner (WebApplicationContextRunner / ReactiveWebApplicationContextRunner for web): you configure it with .withConfiguration(AutoConfigurations.of(...)), run it, and assert via AssertableApplicationContext — e.g. assertThat(context).hasSingleBean(Foo.class) or .doesNotHaveBean(...). That verifies the condition outcome you care about without parsing logs. You can also expose the report through the Actuator 'conditions' endpoint (management.endpoints.web.exposure.include=conditions) for a running app.

code

java · 22 lines
java
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.FilteredClassLoader;
import static org.assertj.core.api.Assertions.assertThat;

class MyAutoConfigurationTests {

    private final ApplicationContextRunner runner = new ApplicationContextRunner()
            .withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class));

    @Test
    void createsBeanWhenPropertyEnabled() {
        runner.withPropertyValues("my.feature.enabled=true")
              .run(ctx -> assertThat(ctx).hasSingleBean(MyService.class));
    }

    @Test
    void backsOffWhenClassAbsent() {
        runner.withClassLoader(new FilteredClassLoader(MyService.class))
              .run(ctx -> assertThat(ctx).doesNotHaveBean(MyService.class));
    }
}

go deeper

for a junior

Awareness that the report can be reached programmatically and via Actuator, even if not the exact API.

for a middle

Know the Actuator conditions endpoint and the ApplicationContextRunner basics.

for a senior

Fluent with ApplicationContextRunner, FilteredClassLoader, and ConditionEvaluationReport.get for tests/tooling.

for a principal

Choose the right tool per situation and reason about per-context reports in hierarchies.

## The report is a bean After the context refreshes, a `ConditionEvaluationReport` object lives in the bean factory. Spring Boot's own logging listener just formats it. You can grab it yourself: ```java ConditionEvaluationReport report = ConditionEvaluationReport.get( (ConfigurableListableBeanFactory) context.getBeanFactory()); ``` Key methods: - `getConditionAndOutcomesBySource()` → `Map<String, ConditionAndOutcomes>` keyed by the source (config class / bean method). Each `ConditionAndOutcomes` iterates `ConditionAndOutcome` pairs; `isFullMatch()` tells you if the source matched. Each `ConditionOutcome` has `isMatch()` and `getMessage()`. - `getExclusions()` → set of excluded class names. - `getUnconditionalClasses()` → set of always-applied class names. - `getParent()` → report of the parent context, if any (relevant with a hierarchy). ## Testing: ApplicationContextRunner The modern, preferred way to test conditions is **`ApplicationContextRunner`** (part of `spring-boot-test`). It spins up a throwaway context lazily and gives you an `AssertableApplicationContext`: ```java new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class)) .withPropertyValues("my.feature.enabled=true") .run(context -> assertThat(context).hasSingleBean(MyService.class)); ``` Variants: `WebApplicationContextRunner` (servlet) and `ReactiveWebApplicationContextRunner` (reactive). You can add user beans (`.withUserConfiguration(...)`), classpath filters (`.withClassLoader(new FilteredClassLoader(SomeClass.class))` to simulate a missing dependency for `@ConditionalOnClass`), and property values. Assertions: `hasSingleBean`, `doesNotHaveBean`, `hasBean`, `getFailure`, etc. This is exactly how Spring Boot tests its own auto-configs. You can even reach the report inside the runner: ```java .run(context -> { ConditionEvaluationReport report = ConditionEvaluationReport.get(context.getSourceApplicationContext().getBeanFactory()); // assert on report.getConditionAndOutcomesBySource() }); ``` ## Actuator endpoint For a running app, the **`conditions`** Actuator endpoint (`/actuator/conditions`) returns the same information as JSON: positiveMatches, negativeMatches, exclusions, unconditionalClasses — per context. Enable with `management.endpoints.web.exposure.include=conditions`. Useful in ops without restarting with `--debug`. ## When to use which - **Ad-hoc, once:** `--debug` and read the log. - **Regression test that a bean is/ isn't created under given conditions:** `ApplicationContextRunner`. - **Live running system, ops diagnosis:** Actuator `conditions` endpoint. - **Custom tooling / programmatic assertions:** `ConditionEvaluationReport.get(...)`. ## Gotchas - `ConditionEvaluationReport.get` returns the report for that specific bean factory; in a parent/child hierarchy each context has its own. - `FilteredClassLoader` is the trick for testing `@ConditionalOnClass`/`@ConditionalOnMissingClass` without actually changing the module's dependencies. - The report reflects decisions at refresh time; it isn't recomputed later.

  • How would you test that @ConditionalOnClass correctly backs off when a dependency is absent, without editing your build file?
    Use ApplicationContextRunner.withClassLoader(new FilteredClassLoader(TheClass.class)); the FilteredClassLoader hides that class from the context so the OnClassCondition fails, then assert doesNotHaveBean.
  • What Actuator endpoint exposes the same information as the --debug report?
    The 'conditions' endpoint (/actuator/conditions), which returns positiveMatches, negativeMatches, exclusions and unconditionalClasses as JSON. It must be exposed via management.endpoints.web.exposure.include.

saying these in an interview costs you the question

  • Claiming the report can only be read from logs
  • Using a full @SpringBootTest to test a single auto-config's conditions instead of ApplicationContextRunner
  • Not knowing FilteredClassLoader exists for simulating missing classes

context