Explain how @RestController is composed from stereotype meta-annotations, and how component scanning discovers it as a bean.
answer
- @Component <- @Controller <- @RestController
- @RestController also carries @ResponseBody
- @AliasFor bridges value to @Component name
- AnnotationTypeFilter(Component) matches transitively
- isHandler() = @Controller OR @RequestMapping (meta-aware)
basics
~20 s@RestController is a composed annotation carrying @Controller (which itself carries @Component) plus @ResponseBody. Spring's component scanning finds @Component transitively through these meta-annotations, so a @RestController class is auto-registered as a bean and recognized as a web handler.
solid answer
~40 sSpring stereotypes are built by meta-annotation composition. @Component is the root; @Controller is meta-annotated with @Component; @RestController is meta-annotated with @Controller and @ResponseBody. During classpath scanning (@ComponentScan / Spring Boot's @SpringBootApplication), the ClassPathScanningCandidateComponentProvider uses an AnnotationTypeFilter for @Component that matches transitively — Spring's MergedAnnotations/AnnotationMetadata walks the meta-annotation graph, so @Controller and @RestController are treated as @Component and their classes become bean definitions. Separately, RequestMappingHandlerMapping detects handler beans by looking for the @Controller stereotype (or @RequestMapping) via AnnotatedElementUtils, which also traverses meta-annotations — that's why @RestController counts as a controller. The @ResponseBody carried by @RestController is likewise discovered by RequestMappingHandlerAdapter through the same merged-annotation lookup. You can compose your own stereotype the same way and scanning will pick it up.
code
java · 13 lines// A custom composed stereotype — scanning discovers it like any @Component
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController // -> @Controller -> @Component, + @ResponseBody
@RequestMapping("/api/v1") // shared base path
public @interface ApiV1Controller {}
@ApiV1Controller
class UserEndpoint {
@GetMapping("/users/{id}")
User get(@PathVariable Long id) { return service.find(id); }
// Registered as a bean, mapped to GET /api/v1/users/{id}, returns JSON
}go deeper
Not expected to know the meta-annotation machinery.
Should know @RestController = @Controller + @ResponseBody and that scanning registers it as a bean.
Should explain transitive @Component matching, isHandler detection, and @AliasFor.
Discusses MergedAnnotations internals, custom composed stereotypes, and separation of bean-scanning vs handler-mapping phases.
## Meta-annotation composition Spring stereotypes form a **hierarchy of composed annotations**: ``` @Component (root stereotype) ^ @Controller (@Component + "web controller" semantics) ^ @RestController (@Controller + @ResponseBody) ``` Concretely: ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Component // <-- @Controller IS a @Component public @interface Controller { @AliasFor(annotation = Component.class) String value() default ""; } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Controller // <-- @RestController IS a @Controller @ResponseBody // <-- ...and carries @ResponseBody public @interface RestController { @AliasFor(annotation = Controller.class) String value() default ""; } ``` Note `@AliasFor`: the `value` on `@Controller`/`@RestController` is aliased to `@Component`'s `value`, so a name you give a `@RestController("myBean")` flows through to the bean name. ## How component scanning finds it 1. `@ComponentScan` (implied by `@SpringBootApplication`) creates a `ClassPathScanningCandidateComponentProvider`. 2. By default it registers an **include filter**: `AnnotationTypeFilter(Component.class)`. 3. That filter matches **not just directly-annotated classes but any class whose annotations are (transitively) meta-annotated with `@Component`**. It uses `MergedAnnotations` / `AnnotationMetadata` to walk the annotation graph. 4. So a `@RestController` class → is `@Controller` → is `@Component` → matches → becomes a `BeanDefinition` → instantiated as a singleton bean. ## How it's recognized as a web handler Bean-ness alone doesn't make it handle requests. `RequestMappingHandlerMapping.isHandler()` returns true for a bean type that is annotated (again, meta-annotation-aware via `AnnotatedElementUtils`) with **`@Controller` OR `@RequestMapping`**. Because `@RestController` is meta-`@Controller`, it qualifies. The mapping then registers each `@RequestMapping`/`@GetMapping` method. ## How @ResponseBody is honored When a handler method returns, `RequestMappingHandlerAdapter` runs its `HandlerMethodReturnValueHandler`s. `RequestResponseBodyMethodProcessor.supportsReturnType()` checks for `@ResponseBody` on the **method or its declaring class**, using merged-annotation lookup — so the class-level `@ResponseBody` inherited from `@RestController` triggers body serialization for every method. ## Rolling your own stereotype Because matching is transitive, you can define a custom composed stereotype and scanning will pick it up: ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @RestController @RequestMapping("/api") public @interface ApiController {} ``` A class annotated `@ApiController` is a bean, a controller, all-body, and base-pathed at `/api`. ## Gotchas - Meta-annotation traversal is why `AnnotatedElementUtils.findMergedAnnotation` (not plain `getAnnotation`) is used internally — a naive `Class.getAnnotation(Controller.class)` on a `@RestController` class returns **null** because it's meta-, not directly, present. - If you write a custom `@ComponentScan` with `useDefaultFilters = false`, you drop the `@Component` include filter and your controllers won't be found unless you re-add filters. - Component scanning only registers classes under the scanned base packages; a `@RestController` outside them is silently ignored.
- Why does Class.getAnnotation(Controller.class) return null on a @RestController class, yet Spring still treats it as a controller?Because @Controller is a *meta*-annotation on @RestController, not directly present. java.lang.reflect's getAnnotation only sees directly-declared annotations. Spring uses AnnotatedElementUtils/MergedAnnotations, which walk the meta-annotation graph, so it finds @Controller transitively.
- What does @AliasFor accomplish on @RestController's value attribute?It aliases the controller's value to @Component's value, so an explicit bean name given via @RestController("foo") propagates to the underlying @Component bean name instead of being ignored.
saying these in an interview costs you the question
- Saying component scanning matches only classes *directly* annotated with @Component
- Claiming @RestController's controller-ness requires an explicit @Controller in addition
- Confusing bean registration (scanning) with handler registration (RequestMappingHandlerMapping) — they're separate steps