How would you index a custom stereotype annotation, and how do you weigh the candidate index against Spring Boot AOT and plain scanning today?
answer
- @Indexed on the custom annotation (with @Component)
- @Component already implies @Indexed
- index = build-time startup win at scale
- fragile: partial classpath, custom-filter fallback
- Boot 3 AOT / native supersedes it
basics
~20 sMeta-annotate your custom stereotype with @Indexed (and usually @Component) so the indexer records classes using it. Weigh the index against Spring Boot 3 AOT: AOT largely supersedes it, so the index mainly helps large classic Spring apps where you control the whole build.
solid answer
~50 sTo index a custom stereotype, put @Indexed on the annotation definition — typically alongside @Component so it also creates beans. The spring-context-indexer then records every class using that annotation under the stereotype in spring.components, and ClassPathScanningCandidateComponentProvider's AnnotationTypeFilter for it becomes index-supported. On trade-offs: the index is a build-time optimization that only pays off at scale (many components, cold-start-sensitive) and only if you keep it complete across all modules. Its correctness is fragile under partial classpaths and it disables itself under custom include filters. In Spring Boot 3, AOT processing and GraalVM native images do far more (pre-computed bean definitions, reflection hints), effectively superseding the index for most modern apps. So my rule: reach for AOT/native for greenfield startup-critical services; consider @Indexed only for large traditional Spring apps not adopting AOT, and enforce index consistency across the build.
code
java · 12 lines@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component // bean stereotype; already meta-annotated with @Indexed
@Indexed // explicit: guarantees index coverage & documents intent
public @interface DomainService {}
@DomainService
public class PricingRules { /* ... */ }
// spring.components will contain:
// com.example.PricingRules=org.springframework.stereotype.Component
// (indexed via the @Component/@Indexed meta-annotation on @DomainService)go deeper
Know a custom stereotype needs @Indexed (or @Component, which implies it) to be indexed.
Explain the meta-annotation mechanics and that the matching AnnotationTypeFilter becomes index-supported.
Weigh index vs scanning: startup win at scale, completeness discipline, silent fallback.
Position against Boot 3 AOT/native, give a decision framework, and treat index adoption as a build-governance policy.
## Indexing a custom stereotype `@Indexed` is transitive through meta-annotations. If you define your own stereotype: ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Component // makes it a bean stereotype @Indexed // ensures classes using it land in spring.components public @interface DomainService {} ``` Actually, because `@Component` already carries `@Indexed`, meta-annotating with `@Component` alone is enough to be indexed. You add `@Indexed` **explicitly** when your custom stereotype is NOT built on `@Component` but you still want index/scan support for it (e.g. a purely marker interface or annotation you match with a custom `AnnotationTypeFilter`). Explicit `@Indexed` also documents intent and future-proofs against the meta-annotation chain changing. For the scan to use the index, the include filter must be index-supported: an `AnnotationTypeFilter(DomainService.class)` qualifies precisely because `DomainService` is (meta-)annotated with `@Indexed`. ## Trade-offs vs. alternatives **Plain runtime scanning** — zero build complexity, always correct, slower startup proportional to classpath size. Fine for small/medium apps. **Candidate index (@Indexed + indexer)** — faster startup by skipping ASM metadata reads. Costs: an extra build-time processor, the discipline to index every module, silent fallback under non-indexable filters, and the partial-classpath correctness hazard. Best for **large, classic Spring applications** where startup time matters and you own the whole build. **Spring Boot 3 AOT / GraalVM native** — the modern answer to startup and footprint. AOT runs at build time too, but produces far more: pre-generated `BeanDefinition`s, proxy classes, and reflection/resource hints, culminating in near-instant startup and native images. It effectively **supersedes** the candidate index for apps that adopt it. ## Decision guidance - Startup-critical, greenfield, willing to adopt build-time processing → **AOT / native image**. - Large legacy Spring app, not moving to AOT, controls all modules → **candidate index** can shave startup. - Small app or heavy custom filters → **just scan**; the index buys little and may not engage. ## Governance note If you adopt the index, treat it as a cross-cutting build concern: enforce the indexer on every module that contributes scanned components, or you invite the partial-coverage bug. Verify by inspecting the packaged `META-INF/spring.components` for completeness.
- If @Component already carries @Indexed, why ever put @Indexed on a custom stereotype explicitly?For stereotypes NOT built on @Component (e.g. a marker you match with a custom AnnotationTypeFilter) you must add @Indexed yourself to get index coverage. Even on @Component-based ones it documents intent and guards against meta-annotation changes.
- Does the candidate index still matter with Spring Boot 3 AOT?Largely no. AOT/native precomputes bean definitions and reflection hints at build time, delivering a bigger startup win than the index. The index remains relevant mainly for large classic Spring apps not adopting AOT.
saying these in an interview costs you the question
- Claiming the index gives the same benefits as AOT/native images
- Saying you must annotate every bean with @Indexed even for @Component-based stereotypes
- Recommending the index for tiny apps or ones dominated by custom filters
- Ignoring the requirement to index all modules consistently