Spring Boot Configuration Loading Explained: How @ComponentScan, @Import, and Auto-Configuration Split Responsibilities

This article focuses on the Spring Boot configuration loading pipeline. It explains the trigger timing, assembly boundaries, and conditional loading model behind @ComponentScan, @Import, and AutoConfiguration.imports, helping you troubleshoot why a configuration class takes effect—or does not. Keywords: Spring Boot, auto-configuration, @Conditional.

The Technical Specification Snapshot

Parameter Details
Tech stack Java, Spring Boot
Core topics Configuration loading, conditional wiring, Starter reuse
Key mechanisms Annotation-driven configuration, SPI-style auto-configuration, conditional Bean registration
Source characteristics Reworked from original CSDN technical content
Star count Not provided in the original article
Core dependencies spring-boot-autoconfigure, spring-context, spring-data-redis (example)

The Differences Between Three Configuration Loading Approaches Define Architectural Boundaries

In Spring Boot, the three most common ways to activate configuration are @ComponentScan, @Import, and AutoConfiguration.imports. All three can place classes into the container, but their trigger models and usage boundaries are fundamentally different.

@ComponentScan works best for regular components inside the same project package structure. @Import is suitable for explicitly importing a small number of cross-package configuration classes. Auto-configuration targets shared modules and Starters, with a model of “register candidates first, then enable them conditionally.”

Comparison Table of the Three Approaches

Loading approach Trigger timing Conditional Typical scenario
@ComponentScan Package scanning at startup No Monolithic applications, business components
@Import During configuration class parsing No Temporary imports, third-party class integration
AutoConfiguration.imports Candidates are collected at startup and then filtered Yes Starters, shared configuration reuse

You can use this table as the first decision point when choosing a design approach.

@ComponentScan Is Essentially Full Scanning Based on Package Paths

@SpringBootApplication already composes @ComponentScan, and by default scans the package of the main application class and all its subpackages. As long as a class is annotated with @Component, @Service, @Repository, or @Configuration, Spring will usually discover and register it.

Its main advantage is simplicity: there is almost no cognitive overhead. But the tradeoff is clear as well: if Spring scans it, Spring registers it, whether or not you actually use it. And once shared configuration lives outside the scan path, @ComponentScan can no longer help.

@SpringBootApplication // By default, scan the package of this main class and its subpackages
public class BlogApplication {

    public static void main(String[] args) {
        SpringApplication.run(BlogApplication.class, args); // Start the Spring Boot application
    }
}

This code shows the most common default scan entry point.

When a Configuration Class Lives in Another Package, You Must Explicitly Expand the Scan Scope

If a configuration class is located outside the package hierarchy of the main application class, you can manually specify the root path with scanBasePackages. However, this approach still belongs to the “scan it and load it” model and does not provide on-demand wiring.

@SpringBootApplication(scanBasePackages = "com.bite") // Manually expand the scan scope
public class BlogApplication {

    public static void main(String[] args) {
        SpringApplication.run(BlogApplication.class, args); // Start and load components under com.bite
    }
}

This solves a scan scope problem, not a configuration reuse or conditional control problem.

@Import Is an Explicit and Unconditional Hard Import Mechanism

@Import skips package scanning and hands the target class directly to Spring for processing. If you write it, Spring imports it. If you remove it, it disappears. This makes it a good fit for importing a small number of cross-module configuration classes or integrating third-party types.

But the drawbacks of @Import are equally obvious: maintenance cost grows linearly with the number of configuration classes, and by default it does not include conditional checks. It cannot express logic such as “only activate this configuration when a dependency exists or when a property is enabled.”

@SpringBootApplication
@Import(RedisConfig.class) // Explicitly import cross-module Redis configuration
public class UserServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args); // Directly register RedisConfig at startup
    }
}

This code demonstrates the core semantics of @Import: force the import.

ImportSelector Can Upgrade Hardcoded Imports into Dynamic Selection

When import logic must evaluate conditions, you can implement ImportSelector and return an array of configuration class names to load. This is more flexible than listing multiple classes directly, but it still belongs to the explicit import pipeline.

public class CustomImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        // Decide which configuration classes to import based on metadata or environment conditions
        return new String[]{RedisConfig.class.getName()};
    }
}

@Import(CustomImportSelector.class) // Let the selector dynamically return configuration classes
class UserServiceApplication {
}

This example shows that @Import can become dynamic, but it is still not the same as the Spring Boot auto-configuration system.

Auto-Configuration Enables Cross-Project Reuse Through AutoConfiguration.imports

Starting with Spring Boot 2.7, auto-configuration classes are registered through META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. This file does not load classes directly. It only declares a list of candidate auto-configuration classes.

The key point is this: during startup, Spring Boot first collects these candidates, then evaluates them one by one using the @Conditional family of annotations. Only configuration classes that satisfy the conditions will enter the container.

@Configuration
@ConditionalOnClass(RedisTemplate.class) // Can only take effect when Redis-related dependencies are present
@ConditionalOnProperty(prefix = "spring.redis", name = "host") // Enable only when a host is configured
public class RedisConfig {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate") // Register the default Bean only when the user has not defined one
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory); // Bind the connection factory
        return template;
    }
}

This code shows the typical structure of an auto-configuration class: the configuration itself is only a candidate, while the conditions decide whether it actually becomes active.

src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.bite.common.config.RedisConfig

This manifest file declares auto-configuration candidate classes to Spring Boot.

@Conditional Is the Real Switch Behind On-Demand Auto-Configuration

Many developers assume that AutoConfiguration.imports means a configuration will become active automatically. In reality, it only completes candidate registration. What actually determines whether a Bean gets registered is the conditional annotation on the configuration class or @Bean method.

Common conditions include classpath presence, configuration property switches, whether a Bean already exists in the container, and whether there is exactly one candidate Bean. This allows shared modules to work by default while still letting application teams override them.

Common Conditional Annotation Mapping Table

Annotation Decision dimension Typical use
@ConditionalOnClass Whether a class exists Dependency detection
@ConditionalOnProperty Whether a property exists or matches Feature toggle control
@ConditionalOnBean Whether a specific Bean exists Dependency prerequisite
@ConditionalOnMissingBean Whether a specific Bean does not exist Default implementation fallback
@ConditionalOnSingleCandidate Whether exactly one candidate Bean exists Resolving auto-wiring ambiguity

These annotations form the core decision language of Spring Boot auto-configuration.

The Redis Auto-Configuration Example Best Demonstrates the Three-Layer Design

A reusable bite-common module should usually not depend on the scan path of a business application, nor should it be tightly bound to every startup class through @Import. A better approach is to turn the Redis configuration into auto-configuration and control its loading timing with conditions.

That way, the default configuration only takes effect when the project includes Redis dependencies, provides connection properties, and does not define its own template Bean. This is the standard engineering model behind a Starter.

spring:
  redis:
    host: 127.0.0.1 # Auto-configuration conditions can only match after the Redis host is configured
    port: 6379

This configuration triggers the matching path behind @ConditionalOnProperty.

The Selection Strategy Must Serve Maintenance Cost and Reuse Goals

If you are dealing with application-internal configuration, prefer @ComponentScan because it is simple and direct. If you only need to temporarily integrate a few cross-package classes, @Import is enough. But if your goal is multi-module sharing, plug-and-play adoption, user override support, and efficient resource usage, you should use auto-configuration.

Do not judge only by whether “it works.” Judge by whether the approach provides the engineering qualities you need: reusability, overridability, extensibility, and debuggability.

Practical Decision Checklist

  • Use @ComponentScan for business Beans inside the project
  • Use @Import for a small number of cross-module configuration classes
  • Use AutoConfiguration.imports for shared infrastructure capabilities
  • Add @ConditionalOnMissingBean when you need a default implementation that users can override

This checklist is well suited for day-to-day architecture reviews.

FAQ

1. Why does my @Configuration class not take effect even though I wrote it?

The three most common reasons are: it is not under the @ComponentScan path; it was not explicitly imported through @Import; or it was registered as auto-configuration, but its @Conditional requirements were not satisfied. When troubleshooting, first check package paths, dependency presence, and whether the required properties are fully configured.

2. Both @Import and auto-configuration work across modules, so why should I still use auto-configuration?

@Import only supports explicit integration and is suitable for a small, fixed set of configuration classes. Auto-configuration supports conditional activation, default Beans, user overrides, and Starter-style distribution, which makes it a better fit for long-term reuse and component governance.

3. What is the difference between AutoConfiguration.imports and the older spring.factories?

Both mechanisms declare auto-configuration classes, but Spring Boot 2.7+ recommends AutoConfiguration.imports. The newer mechanism has a more focused responsibility, clearer configuration semantics, and better aligns with the direction of the current auto-configuration system.

Core Summary

This article systematically breaks down the three core Spring Boot configuration loading mechanisms—@ComponentScan, @Import, and AutoConfiguration.imports—and explains, with @Conditional, why auto-configuration can support on-demand wiring. It is especially useful for understanding the startup pipeline, Starter design, and reusable shared module architecture.