Spring Boot Application Structure: Layout and Component Scanning
Explore Spring Boot project structure conventions, the role of @SpringBootApplication, and how component scanning discovers beans.
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 annotationASSIGNABLE_TYPE— classes extending or implementing a specific typeASPECTJ— classes matching an AspectJ type patternREGEX— classes matching a regex patternCUSTOM— a customTypeFilterimplementation
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
- Placing
@SpringBootApplicationin a deeply nested package — This shrinks the default scan scope to that subtree, causing sibling packages to be missed. - Using overly broad scans — Scanning
comororgroot packages picks up thousands of irrelevant classes. - Mixing component scan with multiple
@SpringBootApplicationclasses — 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,@Controllerclasses are in the main class’s package or a subpackage -
@ComponentScanexplicitly includes any packages outside the default scan hierarchy - No classes exist in the default (unnamed) package
- Profile-specific configuration files follow the
application-{profile}.ymlnaming convention - Test classes are in the same package hierarchy as the main class or specify
classesexplicitly - Exclude filters are reviewed periodically to prevent forgotten internal classes from being scanned
- Custom
TypeFilterimplementations are tested for edge cases - Application starts with expected log output showing component scanning activity
- Bean definition counts match expectations (verifiable via
/actuator/beansendpoint)
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
-
@SpringBootApplicationis on the main class - All stereotype-annotated classes are in the main package or a subpackage
- No classes exist in the default package
- Custom
@ComponentScanincludes the main package when adding additional paths -
@Importis used for explicit configuration classes outside the scan - Profile-specific configs follow
application-{profile}.ymlnaming - Test classes specify
classesexplicitly when scanning differs -
excludeFiltersare reviewed to prevent internal class registration - Actuator
/beansendpoint is used to verify registered beans during development
Interview Questions
@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.
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.
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.
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.
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.
@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.
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.
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.
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.
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.
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.
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.
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.
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
- Spring Boot Reference Documentation — Official documentation covering application properties, auto-configuration, and built-in features.
- Spring Framework Component Scanning — Deep dive into how
ComponentScanAnnotationBeanPostProcessoroperates. - Spring Boot Auto-configuration — How auto-configuration decisions are made and how to override them.
- Maven Standard Directory Layout — Build tool conventions that Spring Boot inherits.
- Baeldung: Spring Boot Basics — Practical tutorials on Spring Boot application structure and component scanning patterns.
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.
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.
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.