Component Scanning Deep Dive
Component Scanning Deep Dive
In the previous lessons you saw how to declare beans explicitly with @Bean methods. Component scanning is the complementary mechanism: Spring walks a set of packages, finds classes annotated with @Component (or any meta-annotation that is itself annotated with it), and registers them as beans automatically. Understanding how scanning works — and how to control its scope precisely — is essential once your application grows beyond a handful of classes.
How Component Scanning Works Under the Hood
When the application context starts, Spring's ClassPathScanningCandidateComponentProvider iterates every .class file on the classpath under the configured base packages. It reads the bytecode metadata (without fully loading the class) and checks for the presence of stereotype annotations. Any matching class is instantiated and registered as a bean.
The stereotype annotations that trigger scanning by default are:
@Component— the generic stereotype; the parent of all others.@Service— signals a service-layer bean (no extra Spring behavior, but documents intent).@Repository— signals a data-access bean; also enables Spring's exception translation for persistence exceptions.@Controller/@RestController— marks a Spring MVC handler.
@Component. That is why scanning picks them up. You can create your own stereotype by annotating your annotation with @Component (or any existing stereotype), and Spring will discover it too.
Declaring Base Packages with @ComponentScan
In a plain Spring 6 application you add @ComponentScan to a @Configuration class. In Spring Boot the @SpringBootApplication annotation already includes it, defaulting to the package of the annotated class. Knowing the explicit form lets you override or augment that default.
Alternatively, use basePackageClasses to supply type-safe markers instead of string literals. This is the recommended style because refactoring tools will track the class rename automatically:
basePackageClasses over string package names. A string survives a package rename silently; the marker class does not compile if moved without updating the reference. One empty package-info.java or a dedicated package/Marker.java interface per root package is all you need.
Include Filters: Expanding What Gets Scanned
By default, scanning picks up any class annotated with a stereotype annotation. You can broaden that with includeFilters. Each filter is a @ComponentScan.Filter that specifies a filter type and the target.
Common filter types (from FilterType):
ANNOTATION— include all classes carrying a given annotation.ASSIGNABLE_TYPE— include all classes that are assignable to a given type (class or interface).REGEX— include classes whose fully-qualified name matches a regular expression.ASPECTJ— include classes matching an AspectJ type pattern.CUSTOM— include classes accepted by a customTypeFilterimplementation.
Example: register every class that implements EventHandler, even if it carries no stereotype annotation:
includeFilters, you often need to disable the default annotation filter. If useDefaultFilters is still true (the default), Spring scans for both stereotype annotations AND your include filter. Set useDefaultFilters = false for an entirely custom ruleset — but then remember to add back any stereotype filter you still want.
Exclude Filters: Narrowing the Scan
Exclude filters are more commonly useful in production code. The most frequent use-cases are isolating a test configuration, avoiding double-registration when multiple @ComponentScan declarations overlap, and skipping generated or third-party classes that live under your package prefix.
Writing a Custom TypeFilter
When the built-in filter types are not expressive enough, implement org.springframework.core.type.filter.TypeFilter. It receives metadata about every candidate class without needing to load it fully (classloading is expensive).
Then reference it with FilterType.CUSTOM:
Lazy Scanning and @Lazy
By default, Spring instantiates all singleton beans eagerly at startup. For large component scans this can noticeably slow boot time. Annotate a component with @Lazy to defer its instantiation until it is first requested:
You can also set @ComponentScan(lazyInit = true) to make every scanned bean lazy by default — useful in developer-mode to speed up local restarts while keeping production eager.
Component Scanning in Spring Boot
Spring Boot's @SpringBootApplication is a composed annotation that includes @ComponentScan with no explicit packages, which means it scans the package of the main class and all sub-packages. This "scan from root" convention is why you should keep your main class at the top-level of your base package:
If you need to scan additional packages (for example, a shared library that lives under a different root), add @ComponentScan alongside @SpringBootApplication — or, better, use scanBasePackages directly on @SpringBootApplication:
Practical Trade-offs and Guidelines
- Keep your base package tight. Scanning every class under
comororgwould be catastrophic for startup time. Always specify the narrowest packages that cover your own code. - Avoid overlapping scans. If two
@Configurationclasses scan the same package, beans will be registered twice (Spring deduplicates by bean name, but duplicate scanning wastes time and can cause subtle ordering bugs). - Separate web and non-web contexts. Classic Spring MVC applications have two contexts — root and servlet. Use
excludeFiltersto keep@Controllerbeans out of the root context and service/repository beans out of the servlet context. - Use explicit
@Beanfor third-party classes. You cannot add@Componentto a class you do not own; declare it as a@Beanin a@Configurationclass instead.
Summary
@ComponentScan automates bean discovery by reading classpath bytecode. basePackages / basePackageClasses define the search territory. includeFilters and excludeFilters — backed by annotation, type, regex, or custom TypeFilter logic — let you describe exactly which classes qualify. Mastering these controls means you can design clean context boundaries, speed up startup, and avoid accidental bean registrations as your codebase scales.