When does ClassPathScanningCandidateComponentProvider actually USE the index versus fall back to full classpath scanning?
answer
- two gates: index present + filters supported
- AnnotationTypeFilter ok only if annotation is @Indexed
- AssignableTypeFilter ok if target @Indexed
- regex/custom/aspectj filter -> fallback
- fallback is silent, correctness over speed
basics
~20 sSpring uses the index only if a spring.components file exists AND every include filter is one the index can answer — annotation filters on @Indexed-marked annotations, or assignable-type filters. If any filter isn't index-supported (e.g. a regex or custom filter), it silently falls back to normal scanning.
solid answer
~40 sTwo conditions must hold. First, a CandidateComponentsIndex must have been loaded (at least one META-INF/spring.components on the classpath). Second, ClassPathScanningCandidateComponentProvider must decide the index can fully answer the current include filters — it calls an internal check (indexSupportsIncludeFilters) over each include filter. An AnnotationTypeFilter is index-supported only if its annotation is itself meta-annotated with @Indexed; an AssignableTypeFilter is supported if the target type is @Indexed (or handled as a stereotype). Custom TypeFilters, RegexPatternTypeFilter, or AspectJ filters are NOT index-supported, because the index doesn't record enough to evaluate them. If any include filter fails the check, the provider ignores the index entirely and does a full ASM scan, guaranteeing correctness. So mixing in a non-indexable filter transparently disables the speedup for that scan.
code
java · 12 lines// This scan CAN use the index: default filters match @Component (meta-@Indexed)
@ComponentScan(basePackages = "com.example")
class FastConfig {}
// This scan CANNOT use the index for its include filter -> falls back to full scan,
// because a REGEX filter is not answerable from spring.components:
@ComponentScan(
basePackages = "com.example",
useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(
type = FilterType.REGEX, pattern = "com\\.example\\..*Service"))
class SlowConfig {}go deeper
Just know the index isn't always used; some filters make Spring scan anyway.
State the two conditions: file present and include filters answerable from the index.
Detail which TypeFilter kinds are index-supported and that fallback is silent and correctness-driven.
Reason about how custom-filter usage silently erodes the startup win and how to audit which scans actually hit the index.
## Two gates before the index is used `ClassPathScanningCandidateComponentProvider.findCandidateComponents(basePackage)` chooses between `addCandidateComponentsFromIndex(index, basePackage)` and `scanCandidateComponents(basePackage)`: 1. **Is an index present?** The provider holds a `CandidateComponentsIndex` (obtained from `CandidateComponentsIndexLoader.loadIndex(classLoader)`), which is non-null only if at least one `META-INF/spring.components` exists. 2. **Can the index answer the current include filters?** The provider calls an internal `indexSupportsIncludeFilters()` that checks EVERY include filter via `indexSupportsIncludeFilter(TypeFilter)`. ## What makes a filter index-supported - **AnnotationTypeFilter** → supported **only** if the annotation type it matches is (meta-)annotated with `@Indexed`. Standard stereotypes qualify because `@Component` carries `@Indexed`. - **AssignableTypeFilter** → supported if the target type is annotated with `@Indexed` / recognized as a stereotype the index tracks (e.g. an indexed interface). - **Everything else** — `RegexPatternTypeFilter`, `AspectJTypeFilter`, custom `TypeFilter` implementations — is **not** supported, because the `spring.components` file only records class→stereotype pairs, not arbitrary predicates over bytecode. ## The fallback is silent and safe If ANY include filter is not index-supported, the provider abandons the index for that call and performs a normal ASM classpath scan. This is by design: the index might be *less complete* than a real scan for that predicate, so Spring prefers correctness over speed. There is no error and usually no log — startup just isn't accelerated. ## Practical implications - A `@ComponentScan` using default stereotype detection or `useDefaultFilters=true` benefits from the index. - Adding `@ComponentScan.Filter(type = FilterType.REGEX, ...)` or a custom filter as an **include** filter will disable index usage for that scan. - **Exclude** filters do not disable the index the same way — the gating is about whether *include* filters can be answered from the index; excludes are applied afterward. ## Related: CandidateComponentsIndex.getCandidateTypes When used, the provider calls `index.getCandidateTypes(basePackage, stereotype)` per stereotype and unions the results, then applies exclude filters and condition evaluation as usual.
- You added a custom include filter and startup got slower again. Why?A custom/regex/AspectJ include filter is not index-supported, so ClassPathScanningCandidateComponentProvider stops using the index for that scan and reverts to a full ASM classpath scan. The index only accelerates scans whose every include filter can be answered from spring.components.
- Why does Spring fall back silently instead of erroring?Because the index may be incomplete for a predicate it can't evaluate; a full scan is strictly safer. Spring prioritizes correctness, so it quietly does the slower scan rather than risk missing beans.
saying these in an interview costs you the question
- Claiming the index is always used whenever spring.components exists
- Thinking a regex/custom include filter still uses the index
- Saying an unsupported filter throws an error rather than falling back
- Believing exclude filters disable the index the way include filters can