Spring Boot Application Structure: Layout and Component Scanning

Explore Spring Boot project structure conventions, the role of @SpringBootApplication, and how component scanning discovers beans.

published: reading time: 24 min read author: GeekWorkBench
Quick Summary

Explore Spring Boot project structure conventions, the role of @SpringBootApplication, and how component scanning discovers beans. The guide uses practical examples to explain introduction to spring boot application structure, standard directory layout and shows how to apply the ideas in a Spring Boot project. It closes with common pitfalls and production checks so you can apply the pattern with fewer surprises.

Spring Boot Application Structure: Layout and Component Scanning

When you bootstrap a Spring Boot project, the framework immediately imposes opinions about where files live, how your main class operates, and which packages Spring scans for components. These conventions are not arbitrary — they reduce configuration friction, enable sensible defaults, and make auto-configuration work without explicit wiring. This guide walks through each layer of that structure so you can work with the grain instead of constantly fighting it.

Introduction to Spring Boot Application Structure

Spring Boot’s philosophy is convention over configuration. Rather than requiring you to wire every bean and specify every component path, the framework ships with defaults that take effect automatically. The application structure — directory layout, main class placement, and component scanning — forms the foundation that makes those defaults work.

Get the main class placement wrong and component scanning breaks without any error message. Violate the package hierarchy and auto-configuration stops firing. These are the kinds of silent failures that eat hours of debugging time — beans do not appear, configuration is mysteriously ignored, and the application starts anyway. Understanding the mechanics prevents this class of problems entirely.

This post covers the complete anatomy of a Spring Boot application’s structural conventions: the Maven or Gradle directory layout, what @SpringBootApplication actually does, how component scanning discovers your beans, and the edge cases that trip up even experienced developers.

Standard Directory Layout

Spring Boot projects follow the Maven (or Gradle) standard directory layout. This is not arbitrary — Spring Boot’s build plugins, test runners, and packaging tools all assume these conventions.

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           └── myapp/
│   │               ├── MyApplication.java    ← Main class (root package)
│   │               ├── config/
│   │               │   └── AppConfig.java
│   │               ├── controller/
│   │               │   └── UserController.java
│   │               ├── service/
│   │               │   └── UserService.java
│   │               ├── repository/
│   │               │   └── UserRepository.java
│   │               └── model/
│   │                   └── User.java
│   └── resources/
│       ├── application.yml                  ← Primary config
│       ├── application-prod.yml             ← Prod profile
│       ├── application-dev.yml              ← Dev profile
│       ├── static/                          ← Static assets (CSS, JS, images)
│       └── templates/                       ← Template files (Thymeleaf, FreeMarker)
└── test/
    └── java/
        └── com/
            └── example/
                └── myapp/
                    └── MyApplicationTests.java

src/main/java — Source Code

All Java source files live here. Spring Boot’s convention is to place the main application class in the root package of your application. Every other class — controllers, services, repositories, entities, configuration classes — should live in a subpackage of that root. This placement is critical because component scanning defaults to the root package.

src/main/resources — Configuration and Assets

The resources folder holds configuration files and static assets. Spring Boot loads application.yml or application.properties automatically. Profile-specific files like application-dev.yml activate when you set the spring.profiles.active property.

The static/ directory serves static content directly via the embedded web server — images, CSS files, JavaScript. The templates/ directory holds view templates for server-side rendering engines like Thymeleaf, FreeMarker, or Mustache.

src/test — Test Sources

Test classes mirror the main source structure. Spring Boot’s test annotations (@SpringBootTest, @WebMvcTest, @DataJpaTest) automatically locate your main class and use the same package scanning rules during testing. Placing your test in the same package as your main class (or a subpackage) ensures component scanning picks up the same beans in tests as in production.

Main Class and @SpringBootApplication Deep Dive

The main class is your application’s entry point. In a Spring Boot application, it does two things: launches the Spring application context and bootstraps auto-configuration.

package com.example.myapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

@SpringBootApplication is not a single monolithic annotation. It is a composed annotation that combines three distinct annotations:

// What @SpringBootApplication actually is:
@SpringBootConfiguration  // ← Mark this class as a source of bean definitions
@EnableAutoConfiguration   // ← Enable Spring Boot's auto-configuration engine
@ComponentScan             // ← Scan this package and subpackages for components

@SpringBootConfiguration

@SpringBootConfiguration is a Spring @Configuration annotation specifically tailored for Spring Boot. It marks the annotated class as a source of bean definitions. You can use regular @Configuration in a Spring Boot application, but @SpringBootConfiguration signals that this is the primary application configuration class — the root of your configuration hierarchy.

@EnableAutoConfiguration

This annotation triggers Spring Boot’s auto-configuration mechanism. Auto-configuration attempts to configure your application based on the dependencies present on the classpath. If spring-boot-starter-web is on the classpath, auto-configuration sets up an embedded Tomcat server and configures DispatcherServlet. If spring-boot-starter-data-jpa is present, it configures an in-memory DataSource and JPA entity manager.

Auto-configuration is additive — it does not override explicit configuration. If you define a WebMvcConfigurer bean, the auto-configured MVC support defers to your explicit configuration.

@EnableAutoConfiguration works by reading spring.factories (or the newer META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports file in Spring Boot 3.x). These files list the auto-configuration classes that should be considered.

@ComponentScan

Component scanning is the mechanism that discovers Spring-managed components — classes annotated with @Component, @Service, @Repository, @Controller, @RestController, @Configuration, and other stereotype annotations — and registers them as beans in the application context.

By default, @ComponentScan scans the package of the annotated class and all of its subpackages. If your main class lives at com.example.myapp, Spring scans com.example.myapp, com.example.myapp.controller, com.example.myapp.service, and so on.

The following diagram illustrates how the three annotations work together:

graph TD
    A["@SpringBootApplication"] --> B["@SpringBootConfiguration"]
    A --> C["@EnableAutoConfiguration"]
    A --> D["@ComponentScan"]

    B --> E["Beans registered in ApplicationContext"]
    D --> F["@Component / @Service / @Repository / @Controller discovered"]
    C --> G["Auto-configured beans from classpath"]

    E --> H["Spring ApplicationContext"]
    F --> H
    G --> H

Component Scanning Mechanics and Custom Scan Paths

Component scanning happens at application startup. The ComponentScanAnnotationBeanPostProcessor scans the configured base packages, detects classes with stereotype annotations, and registers them as bean definitions.

Default Behavior

The default scan base package is the package containing the class annotated with @ComponentScan. Because @SpringBootApplication includes @ComponentScan, the default base package is the package of your main application class.

// Default: scans com.example.myapp and all subpackages
@SpringBootApplication
public class MyApplication { ... }

Custom Scan Paths

There are scenarios where you need to scan packages outside your main class’s subtree. For example, you might have shared components in a separate module or a third-party library that you want Spring to manage.

@SpringBootApplication
@ComponentScan({ "com.example.myapp", "com.example.shared", "com.vendor.utils" })
public class MyApplication { ... }

When you explicitly specify @ComponentScan, you override the defaults entirely — you must include your main package explicitly if you want it scanned along with others. If you want to add packages without losing the default, use @ComponentScan with basePackageClasses:

@SpringBootApplication
@ComponentScan(basePackageClasses = { MyApplication.class, SharedUtils.class })
public class MyApplication { ... }

This approach uses the classes themselves as package anchors, scanning whatever packages those classes belong to.

Filter Types

Component scanning supports include and exclude filters. You can narrow the scan to specific annotation types or classes matching certain patterns:

@SpringBootApplication
@ComponentScan(
    includeFilters = @Filter(type = FilterType.ANNOTATION, classes = Controller.class),
    excludeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Internal.*")
)
public class MyApplication { ... }

Filter types available:

  • ANNOTATION — classes with a specific annotation
  • ASSIGNABLE_TYPE — classes extending or implementing a specific type
  • ASPECTJ — classes matching an AspectJ type pattern
  • REGEX — classes matching a regex pattern
  • CUSTOM — a custom TypeFilter implementation

Spring Boot 3.x and the imports File

In Spring Boot 3.0+, auto-configuration moved from META-INF/spring.factories to META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. This does not directly affect component scanning, but it changes how auto-configured beans are discovered. The principle remains the same: the framework discovers and applies configuration automatically.

Package Naming Conventions and Why They Matter

Spring’s package naming conventions are not stylistic preferences — they directly affect whether component scanning works and whether auto-configuration activates.

The Default Package Problem

Placing classes in the default package (having no package declaration, or using package;) is a common mistake that breaks component scanning. When your main class lives in a named package but other classes live in the default package, those default-package classes are invisible to component scanning.

// DO NOT DO THIS — classes in default package are not scanned
package com.example.myapp;

@SpringBootApplication
public class MyApplication { ... }

// In default package — will NOT be scanned!
public class SomeService { ... }

Hierarchical Package Structure

Organize packages hierarchically by feature or layer:

com.example.myapp/
├── MyApplication.java           ← Root package
├── user/                       ← Feature-based package
│   ├── UserController.java
│   ├── UserService.java
│   ├── UserRepository.java
│   └── User.java
├── payment/
│   ├── PaymentController.java
│   └── PaymentService.java
└── config/                     ← Shared configuration
    └── SecurityConfig.java

Feature-based packaging (grouping by feature rather than by layer) tends to scale better as applications grow, though layered packaging remains common in smaller applications.

Package Scanning Inheritance

Because @ComponentScan defaults to the declaring class’s package, the placement of your main class determines the scanning scope. If you later refactor your main class into a different package, component scanning stops finding classes in the original location. The application still starts, but beans are missing.

When to Use and When NOT to Use

When to Use Custom Component Scanning

Use custom scan paths when:

  • You have shared libraries containing Spring-managed components that live in a different package hierarchy
  • You are migrating a legacy application and want to gradually adopt Spring Boot without restructuring everything at once
  • You have plugin-style architectures where components from different modules need to be discovered

When NOT to Use Custom Component Scanning

Do not use custom scan paths as a workaround for proper package structure. If you find yourself excluding large portions of your own codebase from the scan, that signals a packaging problem, not a scanning problem. The solution is to reorganize packages, not to widen the scan scope indefinitely.

Avoid scanning java. or javax. packages — Spring Boot’s auto-configuration already handles framework-provided components. Scanning additional system packages adds startup latency and can produce unexpected results.

Anti-Patterns

  1. Placing @SpringBootApplication in a deeply nested package — This shrinks the default scan scope to that subtree, causing sibling packages to be missed.
  2. Using overly broad scans — Scanning com or org root packages picks up thousands of irrelevant classes.
  3. Mixing component scan with multiple @SpringBootApplication classes — Each application can have only one main class with @SpringBootApplication; using it on multiple classes creates conflicting application contexts.

Common Pitfalls

Scanning Issues

Problem: A service class is not being injected, resulting in NoSuchBeanDefinitionException.

Common causes:

  • The class is not annotated with a stereotype (@Service, @Repository, etc.)
  • The class lives outside the component scan base packages
  • The class is annotated but has a typo in the annotation name

Problem: Test configuration picks up different beans than production.

When using @SpringBootTest, the test uses the same component scanning rules as the main application. If your test lives in a different package hierarchy, you may need to specify the main class explicitly:

@SpringBootTest(classes = MyApplication.class)

Package Placement

Problem: Moving the main class causes beans to disappear silently.

This is the most insidious pitfall. If you move MyApplication.java from com.example.app to com.example.app.runner, classes in com.example.app.service are no longer scanned by default. The solution is always to keep the main class in the root package of your application.

Spring Boot 3.x Module Issues

When working with multi-module projects, each module that contains Spring-managed components needs its own component scan configuration. If module A depends on module B, and both have @SpringBootApplication, you may end up with two application contexts fighting each other. The solution is to use @Configuration (not @SpringBootApplication) on secondary modules and import their configuration explicitly.

Security Notes

Component scanning has security implications that are easy to overlook.

Exposure through scanning: If your component scan is too broad, internal classes that should not be exposed as Spring beans get registered automatically. Review what is being scanned and exclude sensitive internal classes with explicit filters.

Third-party libraries: Libraries that mix Spring components with non-Spring classes can inadvertently expose beans through component scanning. Use excludeFilters to prevent unwanted classes from being registered:

@SpringBootApplication
@ComponentScan(excludeFilters = @Filter(
    type = FilterType.REGEX,
    pattern = "com\\.example\\.myapp\\.internal\\..*"
))
public class MyApplication { ... }

Sensitive configuration: Classes annotated with @ConfigurationProperties that bind to external configuration should be explicitly scanned. While these are typically in the normal scan path, any class outside the default scan that relies on property binding needs to be included explicitly.

Profile-specific scanning: In some cases, you may want beans to exist only under specific Spring profiles. Use @Profile on your stereotype annotations rather than conditional scanning to control which beans activate in which environments.

Implementation Snippets

Standard Main Class

package com.example.myapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

Main Class with Custom Component Scan

package com.example.myapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan({
    "com.example.myapp",
    "com.example.shared.components",
    "com.vendor.spring-integration"
})
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

Explicit Configuration Without @SpringBootApplication

If you prefer not to use @SpringBootApplication, you can compose the three annotations explicitly:

package com.example.myapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

This is equivalent to @SpringBootApplication but makes the three constituent annotations visible.

Configuration Class in a Non-Scanned Package

If you have a configuration class in a package that falls outside the default scan, you can import it explicitly:

@SpringBootApplication
@Import(com.example.external.SecurityConfig.class)
public class MyApplication { ... }

Observability Checklist

Use this checklist to verify your Spring Boot application structure is properly configured for monitoring and debugging:

  • Main class is in the root package of the application
  • All @Component, @Service, @Repository, @Controller classes are in the main class’s package or a subpackage
  • @ComponentScan explicitly includes any packages outside the default scan hierarchy
  • No classes exist in the default (unnamed) package
  • Profile-specific configuration files follow the application-{profile}.yml naming convention
  • Test classes are in the same package hierarchy as the main class or specify classes explicitly
  • Exclude filters are reviewed periodically to prevent forgotten internal classes from being scanned
  • Custom TypeFilter implementations are tested for edge cases
  • Application starts with expected log output showing component scanning activity
  • Bean definition counts match expectations (verifiable via /actuator/beans endpoint)

Trade-off Table

Decision Pros Cons
Default @ComponentScan Zero configuration, works out of the box Limited to main class package subtree
Feature-based package layout Scales well, clear ownership Requires discipline to maintain consistency
Layer-based package layout Familiar to Spring developers, easy to find types by layer Can create deep hierarchies in large apps
Custom scan paths Integrates external components seamlessly Can slow startup, risks scanning unintended classes
Exclude filters Keeps internal classes out of the bean registry Requires maintenance as code evolves
@Import over custom scan Explicit, no side-effect scanning More verbose, requires knowing what to import

Failure Scenarios

Scenario 1: Bean Not Found After Package Move

You refactor UserService from com.example.myapp.service to com.example.myapp.user.service. The application still starts, but UserController can no longer inject UserService.

Root cause: The new package is outside the default component scan scope. Fix: Move the main class to a package that encompasses both old and new locations, or add an explicit @ComponentScan covering both.

Scenario 2: Duplicate Bean Registration

You define a @Configuration class in a scanned package and also import it explicitly via @Import. Spring detects the conflict and logs a warning but may use unpredictable resolution order.

Root cause: Double registration of the same configuration. Fix: Use either @Import or component scanning, not both for the same class.

Scenario 3: Auto-Configuration Not Firing

After adding a new dependency, the expected auto-configuration does not activate. The application starts but behaves as if the dependency is not present.

Root cause: Explicit configuration overrides auto-configuration, or the dependency’s auto-configuration class is not on the classpath. Fix: Check that the starter dependency is correctly added in the build file and that no explicit @EnableAutoConfiguration(exclude = …) is suppressing it.

Scenario 4: Test Context Differs from Production

Unit tests pass but the application fails at runtime with missing beans.

Root cause: Tests use @MockBean or test-specific configurations that differ from production scanning. Alternatively, tests live in a different package hierarchy that uses a narrower scan. Fix: Ensure tests use the same main class and scanning configuration, or use @SpringBootTest(classes = MyApplication.class) explicitly.

Quick Recap Checklist

  • Main class lives in the root package of the application
  • @SpringBootApplication is on the main class
  • All stereotype-annotated classes are in the main package or a subpackage
  • No classes exist in the default package
  • Custom @ComponentScan includes the main package when adding additional paths
  • @Import is used for explicit configuration classes outside the scan
  • Profile-specific configs follow application-{profile}.yml naming
  • Test classes specify classes explicitly when scanning differs
  • excludeFilters are reviewed to prevent internal class registration
  • Actuator /beans endpoint is used to verify registered beans during development

Interview Questions

1. What does the @SpringBootApplication annotation actually do behind the scenes?

@SpringBootApplication is a composed annotation that combines three distinct annotations. @SpringBootConfiguration marks the class as a source of bean definitions (it is a specialized form of @Configuration). @EnableAutoConfiguration triggers Spring Boot's auto-configuration mechanism, which attempts to configure the application based on classpath dependencies. @ComponentScan enables scanning of the package containing the annotated class and all of its subpackages for Spring-managed components like @Component, @Service, @Repository, and @Controller. These three work together to bootstrap the entire Spring application context with minimal explicit configuration.

2. Why is it important to place the main application class in the root package?

The @ComponentScan annotation (included in @SpringBootApplication) defaults to scanning the package of the annotated class and all subpackages. If the main class lives in com.example.myapp, Spring scans com.example.myapp and every package beneath it — controllers, services, repositories, entities. If you place the main class in a nested package like com.example.myapp.web, classes in com.example.myapp.service fall outside the default scan and will not be registered as beans. This silent failure means the application starts but beans are missing, causing runtime injection failures that are difficult to debug.

3. How do you include components from a package that falls outside the default component scan?

You have two main options. First, you can use @ComponentScan with an explicit list of base packages: @ComponentScan({"com.example.myapp", "com.example.shared"}). However, when you specify packages explicitly, you must include your main package too, since explicit @ComponentScan overrides the defaults entirely. Second, you can use @Import to bring in specific configuration classes: @Import(SharedConfig.class). The @Import approach is more explicit and is preferable when you have a small number of known configuration classes. For large shared libraries, @ComponentScan with base packages is more maintainable.

4. What happens if you place a class in the default (unnamed) package?

Classes in the default package cannot be discovered by component scanning when the main class lives in a named package. Spring's ComponentScanAnnotationBeanPostProcessor uses the package of the @ComponentScan declaring class as the root, and it cannot traverse into the default package from a named package. This means @Component, @Service, @Repository, @Controller, and other stereotype annotations on default-package classes have no effect — the classes are simply not scanned. Additionally, Spring Boot's auto-configuration classes and many third-party starters assume a proper package hierarchy and may not function correctly with default-package components.

5. How does component scanning interact with explicit @Bean definitions?

Component scanning and explicit @Bean definitions coexist and are complementary. Component scanning automatically discovers classes with stereotype annotations and registers them as beans. @Bean methods in @Configuration classes provide explicit bean definitions, often for third-party classes that cannot be annotated or when you need fine-grained control over bean creation. When both apply to the same type (for example, if a @Component-annotated class also has a @Bean method in a configuration class returning that type), Spring detects the conflict and logs a warning. The @Bean definition typically takes precedence, but the exact resolution depends on the configuration. In general, prefer component scanning for your own code and @Bean for integration points that require explicit instantiation logic.

6. What is the difference between @ComponentScan and @Import in Spring Boot?

@ComponentScan automatically discovers and registers Spring-managed components (classes annotated with @Component, @Service, @Repository, @Controller, etc.) by scanning the configured base packages. @Import, on the other hand, explicitly loads specific @Configuration classes without performing any scanning. @ComponentScan is implicit and works based on package conventions, while @Import is explicit and targets individual configuration classes. Use @Import when you have a small, known set of configuration classes outside your scan path. Use @ComponentScan when you want to discover all components within a package hierarchy automatically. Mixing both approaches for the same class causes duplicate bean registration warnings.

7. How does Spring Boot 3.x change auto-configuration discovery compared to earlier versions?

Spring Boot 3.x replaces the legacy META-INF/spring.factories file with META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports for declaring auto-configuration classes. This new mechanism is more explicit and declarative — you list each auto-configuration class on a separate line rather than using key-value properties. The change improves performance (fewer files parsed at startup) and makes it clearer which auto-configurations are active. Component scanning itself is unchanged; the difference applies specifically to how the auto-configuration engine discovers its configuration classes.

8. What are the security implications of overly broad component scanning?

Broad component scanning can inadvertently register internal classes as Spring beans that should not be exposed. These might include classes containing business logic meant to be called only through explicit APIs, or classes with sensitive data that should not be accessible via dependency injection. Additionally, third-party libraries that mix Spring components with non-Spring classes can expose beans through scanning that were never intended to be managed by Spring. Use excludeFilters to keep internal classes out of the bean registry, and regularly audit what is being scanned using the /actuator/beans endpoint to identify unexpected registrations.

9. Why might an application start successfully but fail at runtime with a NoSuchBeanDefinitionException?

This silent failure pattern occurs when component scanning misses the required class. The application starts because the Spring context still initializes, but the missing bean is not registered. Common causes include: the class is not annotated with any stereotype annotation, the class lives outside the component scan base packages, the class was refactored into a different package without updating @ComponentScan, or the class is in the default package which cannot be scanned from a named package. The fix requires reviewing package placement and ensuring the class is within the scan scope or explicitly imported.

10. What is feature-based packaging and how does it compare to layered packaging?

Feature-based packaging groups all related classes for a single feature in one package (e.g., com.example.myapp.user containing UserController, UserService, UserRepository, User). Layered packaging groups classes by technical role (e.g., com.example.myapp.controllers, com.example.myapp.services). Feature-based packaging scales better in large applications because it makes module boundaries explicit and reduces cross-package dependencies. Layered packaging is more familiar to developers coming from traditional Spring backgrounds but can lead to deep package hierarchies and tight coupling across layers. Spring Boot does not enforce either approach, but consistency matters more than the specific choice.

11. How do you diagnose component scanning issues during development?

Enable debug logging for component scanning by setting logging.level.org.springframework.context.annotation=DEBUG in your application properties. This outputs which packages are being scanned and which classes are registered as beans. The Spring Boot Actuator /beans endpoint lists all registered beans and their sources. You can also use @ComponentScan with basePackageClasses as an anchor to verify the scan base is correct. For missing beans specifically, check that the class has a stereotype annotation, verify the package is under the scan root, and confirm no excludeFilters are inadvertently filtering the class.

12. What happens when you have multiple @Configuration classes scanning the same package?

When multiple @Configuration classes register beans for the same type, Spring resolves conflicts based on priority. @Configuration classes with higher @Order values (lower priority number) take precedence. If no explicit ordering exists, the behavior depends on which configuration class is processed first, which is not deterministic. This can cause subtle bugs where different beans are injected depending on startup order. To avoid this, use @Primary on the preferred bean or consolidate configuration into a single class. Also be aware that @ComponentScan on multiple classes scanning the same packages causes duplicate bean registration warnings.

13. What is the role of ComponentScanAnnotationBeanPostProcessor in the Spring container?

ComponentScanAnnotationBeanPostProcessor is a BeanFactoryPostProcessor that intercepts the Spring container lifecycle after context initialization begins. It reads the @ComponentScan annotation configuration (base packages, include/exclude filters), scans those packages for stereotype-annotated classes, and registers each discovered class as a bean definition in the BeanDefinitionRegistry. It operates very early in the container lifecycle, before regular BeanPostProcessors run. This is why component-scanned beans can be further processed by other post-processors like @Autowired, but any post-processor you define cannot itself be discovered by component scanning in the same context.

14. Can you use @ComponentScan and @Import simultaneously for the same configuration class?

Technically yes, but it produces duplicate bean registration warnings and is not recommended. If a class is both in a scanned package and explicitly imported via @Import, Spring registers it twice as two separate bean definitions. The framework logs a warning but continues. The resolution order between the two registrations depends on processing order, making behavior unpredictable. Choose one approach: either let component scanning discover the class naturally by placing it in the correct package, or use @Import for explicit registration. Do not use both for the same class.

Further Reading

Conclusion

Spring Boot’s application structure conventions are not arbitrary constraints — they are the mechanism by which the framework’s most powerful features work automatically. The main class lives in the root package so that @ComponentScan can discover everything beneath it without explicit configuration. @SpringBootApplication combines the three annotations that make this work: @Configuration, @EnableAutoConfiguration, and @ComponentScan. The Maven and Gradle directory conventions are what the build plugins, test runners, and packaging tools all depend on.

The most common structural problems — beans that fail to inject silently, auto-configuration does not fire, tests that behave differently from production — almost always trace back to package placement or component scanning decisions. When you understand how @ComponentScan determines its base package and what happens when you override it explicitly, these problems become avoidable rather than mysterious.

Keep the main class at the root of your package hierarchy. If you need components outside that subtree, add explicit @ComponentScan paths or use @Import for specific configuration classes. Avoid the temptation to scan overly broad packages as a workaround for a package structure problem — reorganize instead.

Category

Related Posts

Spring Boot Build Tools: Maven & Gradle

Configure Maven and Gradle for Spring Boot projects—plugins, dependency management, packaging JARs and WARs, and build automation essentials.

#spring-boot #spring-boot-roadmap #learning-path

Embedded Web Servers in Spring Boot: Tomcat, Jetty, Undertow

Configure embedded servers in Spring Boot: compare Tomcat, Jetty, and Undertow, tune thread pools, enable access logs, and switch implementations.

#spring-boot #spring-boot-roadmap #learning-path

JUnit 5 & Jupiter: Lifecycle, Nested & Parameterized Tests

Explore JUnit 5 Jupiter features: master test lifecycle annotations, organize tests with @Nested, and parameterize tests with @CsvSource and @MethodSource.

#spring-boot #spring-boot-roadmap #learning-path