What does the ComponentScan annotation do
In Spring Boot, @ComponentScan is automatically included as part of the primary @SpringBootApplication meta-annotation.
🟢 Junior Level
@ComponentScan instructs the Spring IoC container which package hierarchies to scan in order to discover and register Spring beans annotated with stereotype annotations (@Component, @Service, @Repository, @Controller, and @Configuration).
In Spring Boot, @ComponentScan is automatically included as part of the primary @SpringBootApplication meta-annotation.
package com.example.shop;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// Automatically triggers scanning for package com.example.shop and all child subpackages:
// com.example.shop.service, com.example.shop.controller, com.example.shop.repository
@SpringBootApplication
public class ShopApplication {
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class, args);
}
}
Manual Configuration
If services, repositories, or shared libraries reside outside of your main application’s package hierarchy, you can configure scanning explicitly on a configuration class:
@Configuration
@ComponentScan(basePackages = { "com.example.shop", "com.example.common" })
public class AppConfig { }
Default Scanning Rule: If no package attributes are specified, @ComponentScan defaults to scanning the declaring class’s own package and all its child subpackages.
🟡 Middle Level
Attributes of the @ComponentScan Annotation
Spring provides rich configuration options for customized component discovery:
@Configuration
@ComponentScan(
// 1. String-based package paths
basePackages = "com.example.service",
// 2. Type-safe package scanning via marker classes (Best Practice)
basePackageClasses = { ServiceMarker.class, RepositoryMarker.class },
// 3. Exclude specific classes or stereotypes from registration
excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = LegacyBillingService.class
),
// 4. Include custom annotations in component detection
includeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = CustomComponent.class
),
// 5. Toggles scanning for default stereotypes (@Component, @Service, etc.)
useDefaultFilters = true
)
public class AdvancedScanConfig { }
The 5 Filter Types (FilterType)
Spring supports five strategies for filtering candidate components:
FilterType.ANNOTATION: Matches when the target class is annotated with a specific annotation.FilterType.ASSIGNABLE_TYPE: Matches classes that extend or implement a specified class or interface.FilterType.REGEX: Matches class names against a regular expression pattern (e.g..*Stub.*).FilterType.ASPECTJ: Matches classes using an AspectJ type pattern expression.FilterType.CUSTOM: Evaluates a custom implementation oforg.springframework.core.type.filter.TypeFilter.
Why basePackageClasses is Superior to basePackages
Configuring package boundaries using string literals (basePackages = "com.example.service") introduces hidden fragility:
- If a developer renames or moves a package using IDE refactoring tools, string literals are easily overlooked.
- Typos fail silently during compilation and only surface as fatal
NoSuchBeanDefinitionExceptionerrors at application runtime.
By using basePackageClasses, you reference a Java class (typically an empty marker interface or class located in the target package):
@ComponentScan(basePackageClasses = ServiceMarker.class)
Benefits:
- Compile-Time Verification: Any package movement or typo causes an immediate compilation failure.
- Refactoring Resilience: Renaming or moving packages in an IDE automatically updates Java class imports everywhere.
🔴 Senior Level
Under the Hood: The ASM Bytecode Scanner (Zero Classloading)
A widespread misconception is that Spring uses standard Java Reflection (Class.forName()) to scan packages and find @Component annotations.
Why Reflection is NOT Used:
- Metaspace Bloat: Reflectively loading thousands of
.classfiles from disk into JVM memory consumes massive amounts of Metaspace. - Static Initializer Traps: Loading a class via
Class.forName()immediately executes its static initialization blocks (static { ... }). If a static block accesses an unconfigured database or missing file system resource, the application crashes before the Spring container is even assembled.
The ASM Bytecode Pipeline (ClassPathBeanDefinitionScanner):
- During
invokeBeanFactoryPostProcessors,ConfigurationClassPostProcessorinvokesClassPathBeanDefinitionScanner. - The scanner translates package paths into resource queries:
classpath*:com/example/**/*.class. - For every
.classfile found, Spring invokesSimpleMetadataReaderFactory. - Using the low-level ASM bytecode library, Spring reads the raw byte stream of the compiled
.classfile directly from disk or JAR archives. - It inspects the bytecode’s constant pool without loading the class into the JVM
ClassLoader. - ASM extracts the class name, superclass, interfaces, and annotations matching
RetentionPolicy.RUNTIME. - Spring constructs a lightweight
ScannedGenericBeanDefinitionand registers it withDefaultListableBeanFactory. - The actual Java class is loaded into the JVM ClassLoader only at the very last moment—when
createBeanInstance()is called during singleton instantiation!
Startup Acceleration: spring-context-indexer
In large enterprise microservices with tens of thousands of classes, scanning JAR archives and reading bytecodes with ASM can consume 5 to 15 seconds of startup time.
The Solution: Compile-Time Indexing
Add the indexer annotation processor to your dependencies:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<optional>true</optional>
</dependency>
Execution Mechanics:
At Compilation (javac):
spring-context-indexer analyzes source files and generates a static manifest:
META-INF/spring.components
File Contents:
com.example.service.UserService=org.springframework.stereotype.Service
com.example.repository.OrderRepository=org.springframework.stereotype.Repository
At Application Startup:
ClassPathScanningCandidateComponentProvider detects CandidateComponentsIndex.
Instead of recursively scanning the file system and JAR bytecodes,
Spring loads the pre-computed index instantly!
Cold start scanning time drops to near zero.
4 Tricky Questions
1. Does Spring load classes into JVM memory via the ClassLoader when executing @ComponentScan?
Answer: No, it does not. Spring explicitly avoids loading classes during the scanning phase.
Instead, ClassPathBeanDefinitionScanner utilizes the ASM bytecode parsing library via SimpleMetadataReaderFactory. ASM parses the raw .class file byte streams directly from disk or JAR archives, inspecting the bytecode constant pools and annotation tables.
This enables Spring to read class names, interfaces, and annotations without loading classes into the JVM’s Metaspace, and critically, without executing static { ... } initialization blocks. The class is only loaded by the ClassLoader later when an actual bean instance is constructed in the heap.
2. Why is basePackageClasses preferred over string-based basePackages?
Answer:
basePackages uses string literals (e.g., "com.example.service"). String literals are not validated by the Java compiler. If a package is renamed or moved during refactoring, strings are easily missed, resulting in silent scan failures and runtime NoSuchBeanDefinitionException crashes.
basePackageClasses accepts Java class references (e.g., ServicePackageMarker.class). This provides strong compile-time type safety:
- If the package is moved, IDE refactoring tools automatically update the import statements.
- Any misspelling or invalid reference results in an immediate compilation error before tests or applications ever run.
3. What does the spring-context-indexer dependency do, and how does it affect application startup?
Answer:
spring-context-indexer is an annotation processor that runs during the build/compilation phase (javac). It detects all classes annotated with @Component (and derived stereotypes) and compiles a static metadata index file located at META-INF/spring.components.
During application startup, ClassPathScanningCandidateComponentProvider checks for the presence of this file. If found, Spring completely bypasses recursive classpath and JAR bytecode scanning. It directly reads the pre-compiled index, reducing container startup time by 2x to 4x in large codebases.
4. How can you configure @ComponentScan to completely disable standard @Component stereotypes and scan exclusively for a custom domain annotation?
Answer:
You must set useDefaultFilters = false and register your custom annotation using an includeFilters declaration:
@Configuration
@ComponentScan(
basePackages = "com.example.domain",
useDefaultFilters = false, // Disables standard @Component, @Service, @Repository, etc.
includeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = DomainService.class // Only classes with @DomainService will be registered as beans
)
)
public class DomainScanConfig { }
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
@ComponentScanspecifies the package boundaries where Spring scans for stereotype-annotated classes (@Component,@Service,@Repository,@Controller,@Configuration) to register them asBeanDefinitions.
- Defaults to the package of the declaring class and all its subpackages (included automatically in
@SpringBootApplication).- Internal Mechanics: Uses the low-level ASM bytecode library to read
.classfiles without loading them into the JVMClassLoaderor triggeringstaticinitializer blocks.- Best Practice: Use
basePackageClassesinstead of stringbasePackagesfor compile-time safety and refactoring resilience.- Optimization: Use
spring-context-indexerto generateMETA-INF/spring.componentsat compile time, eliminating runtime classpath scanning.
ComponentScan Filter Types Summary
| FilterType | Mechanism | Typical Use Case |
|---|---|---|
ANNOTATION |
Matches class-level annotation | Include custom stereotypes or exclude specific annotations |
ASSIGNABLE_TYPE |
Checks inheritance / instanceof |
Exclude legacy classes or specific interfaces |
REGEX |
Matches class name pattern | Exclude test stubs (e.g. .*Mock.*) |
CUSTOM |
Implements TypeFilter SPI |
Complex conditional scanning logic |
Red Flags (DO NOT Say)
- ❌ “ComponentScan uses standard Java Reflection (
Class.forName()) to find classes on disk.” (It uses ASM bytecode analysis to avoid classloading and static initialization). - ❌ “A class’s
staticblock runs when it is discovered by@ComponentScan.” (Static blocks run only when the JVM loads the class during bean instantiation). - ❌ “You must add
@ComponentScanto every@Serviceand@Repositoryclass.” (It is declared once on the configuration root class). - ❌ “spring-context-indexer scans components dynamically during HTTP requests.” (It is a compile-time annotation processor generating a static index).