Foundation · Understand

How Auto-configuration Works

Boot decides what to configure by evaluating classpath and bean conditions at startup; learn to read those decisions instead of guessing at them.

Primary source: Boot 3.5 auto-configuration

20 min

Lesson 5 of 5

1d streak

The Architectural Question

When an expected auto-configured bean behaves unexpectedly or goes missing, guessing costs hours; the Condition Evaluation Report proves which conditions matched, which failed, and which user bean triggered back-off.

What this guide proves

  • @EnableAutoConfiguration
  • AutoConfiguration.imports
  • @ConditionalOnClass

The cost of manual infrastructure

Before Spring Boot, starting a web application required assembling infrastructure by hand. You had to declare a DispatcherServlet, configure content negotiation, register Jackson message converters, and set up an embedded server factory. This boilerplate was identical across projects, but hiding it inside a closed framework prevented teams from customizing the beans they actually needed.

Auto-configuration solves this by turning common infrastructure into conditional configuration. Boot provides the defaults, but evaluates conditions at startup to ensure it never overrides your explicit choices.

Discovery, conditions, and back-off

Auto-configuration is not scanning. When you use @SpringBootApplication, Boot scans your own packages for @Component and @Bean methods. Separately, @EnableAutoConfiguration tells Boot to read META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports from every jar on the classpath. These imports name candidate configuration classes.

  1. 01Candidate discovery: Boot loads the list of candidate configuration classes from imported framework modules.
  2. 02Condition evaluation: Boot evaluates @Conditional annotations on each candidate against the current classpath, properties, and existing beans.
  3. 03Registration: Candidates whose conditions match are registered as BeanDefinitions.
  4. 04Instantiation: The Spring container creates the actual bean instances from those definitions.

Condition evaluation is evidence-based

Boot does not guess. It checks if a required class exists (@ConditionalOnClass), if you already provided a bean of that type (@ConditionalOnMissingBean), or if a property is enabled (@ConditionalOnProperty).

Tracing Jackson auto-configuration

Consider JacksonAutoConfiguration in Spring Boot 3.5. It does not apply unconditionally to every application. Its declaration proves it only activates when Jackson is actually on the classpath.

snippet.java
@AutoConfiguration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
    static class JacksonObjectMapperConfiguration {

        @Bean
        @Primary
        @ConditionalOnMissingBean
        ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
            return builder.createXmlMapper(false).build();
        }
    }
}

The class-level @ConditionalOnClass ensures Boot ignores this configuration entirely if your build does not include Jackson. A positive outer match does not guarantee every nested configuration matches. The nested JacksonObjectMapperConfiguration evaluates its own @ConditionalOnClass(Jackson2ObjectMapperBuilder.class) before deciding to evaluate the @Bean method, which finally checks @ConditionalOnMissingBean to back off if you define your own ObjectMapper. This is the sanctioned override path.

Proving back-off with ApplicationContextRunner

We can prove this behavior programmatically using Boot's ApplicationContextRunner. This utility lets us run a focused context test with specific auto-configurations and our own beans.

snippet.java
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
    .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class));

@Test
void autoConfigurationBacksOffWhenUserBeanIsPresent() {
    this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> {
        assertThat(context).hasSingleBean(ObjectMapper.class);
        assertThat(context).getBean("myCustomMapper").isSameAs(context.getBean(ObjectMapper.class));
    });
}

@Configuration(proxyBeanMethods = false)
static class UserConfiguration {
    @Bean
    ObjectMapper myCustomMapper() {
        return new ObjectMapper();
    }
}

When we supply UserConfiguration, Boot evaluates JacksonAutoConfiguration, sees our myCustomMapper bean, and skips its own default. The test proves that the container holds exactly one ObjectMapper, and it is the one we defined.

Reading the condition evaluation report

When a bean is missing or unexpected, do not guess. Run your application with the --debug flag to print the Condition Evaluation Report. This trace explains exactly why every candidate matched or failed.

Terminal / Diagnostic Output
Positive matches:
-----------------
   JacksonAutoConfiguration matched:
      - @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)

   JacksonAutoConfiguration.JacksonObjectMapperConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.http.converter.json.Jackson2ObjectMapperBuilder' (OnClassCondition)

Negative matches:
-----------------
   JacksonAutoConfiguration.JacksonObjectMapperConfiguration#jacksonObjectMapper:
      - @ConditionalOnMissingBean (types: com.fasterxml.jackson.databind.ObjectMapper; SearchStrategy: all) found beans of type 'com.fasterxml.jackson.databind.ObjectMapper' myCustomMapper (OnBeanCondition)

This report confirms the outcome of our experiment: the outer configuration class and its nested configuration matched because Jackson was on the classpath, but the specific bean method backed off because it found myCustomMapper.

Diagnosing missing beans vs. false conditions

A common misconception is assuming that because Tomcat started on port 8080, MVC auto-configuration succeeded and any subsequent 404 must simply be an unmapped path. In reality, Tomcat starting does not even prove that ServletWebServerFactoryAutoConfiguration ran—if an engineer manually declared a custom @Bean public TomcatServletWebServerFactory servletWebServerFactory(), the default auto-configuration completely backed off due to @ConditionalOnMissingBean, yet Tomcat still started! Tomcat starting only proves that a web server factory bean was resolved and bound to a port. A subsequent 404 can stem from three distinct architectural failure modes: MVC auto-configuration did not match, the controller bean was never instantiated, or handler mappings did not register the expected URI.

Use a structured three-step diagnostic tree instead of guessing: (1) Run with --debug and inspect the Condition Evaluation Report to verify that DispatcherServletAutoConfiguration and WebMvcAutoConfiguration matched. (2) Query /actuator/beans to confirm whether your @RestController exists in the ApplicationContext. If absent, do not jump to package-scanning conclusions: an absent bean may be caused by package boundaries outside the @SpringBootApplication scan tree, but equally by @Profile mismatches (e.g. @Profile("dev") running under prod), conditional evaluations (@ConditionalOnProperty, @ConditionalOnClass, @ConditionalOnBean), or @ComponentScan.Filter exclusions. Inspect the Condition Evaluation Report under "Negative matches" to identify the exact condition that backed off. (3) Query /actuator/mappings to verify that DispatcherServlet mapped the exact HTTP verb and path—if the bean exists in the context but returns 404, inspect server.servlet.context-path, path variable regular expressions, trailing slash mismatches, or unmapped HTTP methods.

Active Recall Checkpoint

Your custom controller at com.example.api.OrderController returns 404, but Tomcat started successfully on port 8080 from com.example.app.Application. Does Tomcat starting prove ServletWebServerFactoryAutoConfiguration succeeded?

Show answer & explanation

No. Tomcat starting only proves that a ServletWebServerFactory bean was resolved and initialized—a manually declared ServletWebServerFactory bean causes ServletWebServerFactoryAutoConfiguration to back off via @ConditionalOnMissingBean while Tomcat still starts. Furthermore, an absent controller from /actuator/beans does not uniquely prove scanning failed: it may also be excluded by an active @Profile, a @ConditionalOnProperty condition that evaluated false, or custom ComponentScan filter rules. Always check the Condition Evaluation Report (--debug) to isolate the exact cause.

How to change framework defaults

Use it when

Rely on application properties (e.g., server.port) when the auto-configured bean is acceptable but needs minor tuning.

Reconsider when

Define your own @Bean to trigger back-off when properties cannot express your requirement. Use exclusions only when replacing the entire capability.

Runtime Architecture & Flow

High-value visual model

Auto-configuration Decision Tree

Auto-configuration is conditional Java configuration. The important question is which condition matched, backed off, or lost to your explicit bean.

1. Is the library on the classpath?

@ConditionalOnClass checks whether the feature is available.

2. Is the feature enabled by config?

@ConditionalOnProperty lets external configuration turn infrastructure on or off.

3. Did the user define a bean?

@ConditionalOnMissingBean backs off when application code takes control.

4. Register default infrastructure

Boot contributes the default bean only after the conditions pass.

Conditional decision tree: Use the condition evaluation report to prove why Boot created or skipped a bean.

Working Implementation

Code Walkthrough

Inspect the Condition Evaluation Report with `--debug` or ApplicationContextRunner to verify JacksonAutoConfiguration matches, then declare a custom ObjectMapper bean and observe @ConditionalOnMissingBean back-off.

JacksonAutoConfigurationTest.java
package com.example.autoconfig;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import static org.assertj.core.api.Assertions.assertThat;

class JacksonAutoConfigurationTest {

    private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
            .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class));

    @Test
    void baselineSuppliesDefaultObjectMapper() {
        this.contextRunner.run((context) -> {
            assertThat(context).hasSingleBean(ObjectMapper.class);
            assertThat(context).hasBean("jacksonObjectMapper");
        });
    }

    @Test
    void autoConfigurationBacksOffWhenUserBeanIsPresent() {
        this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> {
            assertThat(context).hasSingleBean(ObjectMapper.class);
            assertThat(context).getBean("myCustomMapper")
                    .isSameAs(context.getBean(ObjectMapper.class));
            assertThat(context).doesNotHaveBean("jacksonObjectMapper");
        });
    }

    @Test
    void negativeControlWithoutAutoConfigurationYieldsNoMapper() {
        new ApplicationContextRunner().run((context) -> {
            assertThat(context).doesNotHaveBean(ObjectMapper.class);
        });
    }

    @Test
    void conditionalOnClassDisablesCandidateWhenLibraryIsAbsent() {
        this.contextRunner
                .withClassLoader(new FilteredClassLoader(ObjectMapper.class))
                .run((context) -> {
                    assertThat(context).doesNotHaveBean(ObjectMapper.class);
                });
    }

    @Configuration(proxyBeanMethods = false)
    static class UserConfiguration {
        @Bean
        ObjectMapper myCustomMapper() {
            return new ObjectMapper();
        }
    }
}
The baseline test verifies that JacksonAutoConfiguration creates jacksonObjectMapper by default.
UserConfiguration forces the framework bean to back off: the container holds exactly one mapper and it is myCustomMapper.
The negative control proves the absence of a mapper when JacksonAutoConfiguration is not registered.
FilteredClassLoader simulates removing Jackson from the classpath, proving @ConditionalOnClass disables the candidate entirely.

Synthesis & Production Verification

Production Acceptance Criteria

  • Read positive and negative matches in the Condition Evaluation Report
  • Demonstrate @ConditionalOnClass matching when Jackson is present on the classpath
  • Verify user-declared @Bean triggers @ConditionalOnMissingBean back-off using ApplicationContextRunner or live debug output

Architectural & Technical Review

Core Interview Questions

Q01How does Spring Boot decide whether an auto-configuration applies?Show answer

Boot discovers candidate configuration classes and evaluates their conditions against runtime evidence such as available classes, properties, application type, resources, and existing beans.

Q02What does @ConditionalOnMissingBean achieve?Show answer

It creates a default only when the application has not already supplied the guarded capability, which gives application configuration precedence without disabling all auto-configuration.

Q03How should you diagnose an expected auto-configured bean that is absent?Show answer

Read the candidate in the condition evaluation report and follow its failed condition to the missing class, property, application type, resource, or existing-bean state.

Production Pitfalls

Critical Anti-Patterns

  • Pitfall: Assuming a starter guarantees every bean — The starter makes classes available, but every candidate still evaluates its own conditions.
  • Pitfall: Using a broad exclusion for one unwanted default — Prefer a documented property or narrow user bean unless the application intends to own the entire capability.

Module Certification Quiz

Prove Module Mastery

Comprehensive question set covering all lessons in this module.

Take Module Quiz

Primary Documentation & Specifications

Lesson Primary References