What are the main BeanDefinition implementations (RootBeanDefinition, GenericBeanDefinition, ChildBeanDefinition, AnnotatedBeanDefinition) and when is each used?
answer
- Generic = modern all-purpose
- Root = runtime merged/resolved
- Child = legacy parent-inheritance
- Annotated = carries AnnotationMetadata (ASM, no class load)
- Scanned vs AnnotatedGeneric vs ConfigurationClass
basics
~10 sGenericBeanDefinition is the general-purpose, modern definition you create. RootBeanDefinition is the fully-resolved internal one the container uses at runtime. ChildBeanDefinition inherits from a parent (legacy). AnnotatedBeanDefinition adds annotation metadata for scanned/@Bean components.
solid answer
~30 s`GenericBeanDefinition` is the standard, all-purpose implementation for programmatic/registered definitions — it can optionally point at a parent by name, replacing the old rigid pair. `ChildBeanDefinition` is the legacy way to declare a definition that inherits settings from a named parent (you'd now use GenericBeanDefinition with setParentName). `RootBeanDefinition` is used internally: the container computes a *merged* RootBeanDefinition per bean at runtime, flattening any parent/child hierarchy into one fully-resolved definition it actually instantiates from. `AnnotatedBeanDefinition` is an interface (implemented by `ScannedGenericBeanDefinition` for classpath-scanned `@Component`s and `AnnotatedGenericBeanDefinition`/`ConfigurationClassBeanDefinition` for `@Bean` methods) exposing `AnnotationMetadata`/`MethodMetadata` so processors can read annotations without loading the class.
code
java · 13 lines// Reading annotation metadata from a scanned component's definition
BeanDefinition bd = registry.getBeanDefinition("paymentService");
if (bd instanceof AnnotatedBeanDefinition abd) {
AnnotationMetadata meta = abd.getMetadata();
boolean hasTx = meta.hasAnnotation("org.springframework.transaction.annotation.Transactional");
// class not necessarily loaded yet — metadata read via ASM during scanning
}
// Modern parent/child without ChildBeanDefinition:
GenericBeanDefinition child = new GenericBeanDefinition();
child.setParentName("baseConnectionSettings");
child.getPropertyValues().add("database", "orders");
registry.registerBeanDefinition("ordersConnection", child);go deeper
Know GenericBeanDefinition is the one you create and RootBeanDefinition is internal.
Distinguish all four, know Generic replaced the Root/Child split, and that Annotated carries AnnotationMetadata.
Explain the merged RootBeanDefinition role and ASM-based metadata enabling filter-before-load scanning.
Discuss ConfigurationClassBeanDefinition/MethodMetadata, deferred class-loading trade-offs, and how metadata drives ConfigurationClassPostProcessor conditional evaluation.
## The hierarchy All of these implement `BeanDefinition` and extend `AbstractBeanDefinition`, which holds the common fields (class, scope, flags, values). ### `GenericBeanDefinition` The **modern, general-purpose** implementation. Since Spring 2.5 it is the one-stop class for reading and programmatic registration. It has a settable `parentName`, so a single class covers both standalone and child roles — that's why it superseded the old `RootBeanDefinition`/`ChildBeanDefinition` split for user code. `BeanDefinitionBuilder.genericBeanDefinition(...)` produces one of these. ### `ChildBeanDefinition` (legacy) Represents a definition that **inherits configuration from a named parent** definition. It *requires* a parent name (immutable), so it's inflexible. Modern code uses `GenericBeanDefinition` with `setParentName(...)` instead. Parent/child lets you factor out shared property values into an `abstract` parent template. ### `RootBeanDefinition` The **container's runtime working definition**. Two roles: (1) historically, a standalone (parentless) definition; (2) more importantly today, the **merged** definition. When a bean has a parent, the factory calls `getMergedBeanDefinition(name)` and produces a `RootBeanDefinition` that flattens parent + child into one fully-resolved, self-contained definition. The factory always instantiates from a merged RootBeanDefinition, never directly from a child. It also caches resolved types here. ### `AnnotatedBeanDefinition` (interface) Extends `BeanDefinition` and adds `getMetadata()` returning `AnnotationMetadata`, plus `getFactoryMethodMetadata()`. Implementations: - **`ScannedGenericBeanDefinition`** — created by `ClassPathBeanDefinitionScanner` for `@Component`-annotated classes found on the classpath. Metadata is read via ASM **without loading the class**, so scanning is cheap and can filter by annotations before class-loading. - **`AnnotatedGenericBeanDefinition`** — for classes registered directly (e.g. via `AnnotatedBeanDefinitionReader`, `@Configuration` classes). - **`ConfigurationClassBeanDefinition`** (internal) — for beans produced by `@Bean` methods; carries `MethodMetadata`. The annotation metadata is what lets `ConfigurationClassPostProcessor` and `AutowiredAnnotationBeanPostProcessor` discover `@Scope`, `@Lazy`, `@Primary`, `@Conditional`, etc. ## Gotchas - Don't instantiate `RootBeanDefinition` for your own registrations — use `GenericBeanDefinition`/`BeanDefinitionBuilder`; let the container create merged RootBeanDefinitions. - `ScannedGenericBeanDefinition`'s value is deferred class-loading; if you `getBeanClassName()` you get a string, and the class is only loaded when needed. - A merged RootBeanDefinition is a **copy** — mutating it after merge (outside a proper post-processor phase) won't affect the source child. ## When to use - Reading/registering programmatically → `GenericBeanDefinition` (usually via `BeanDefinitionBuilder`). - Inheritance → `GenericBeanDefinition.setParentName` + an `abstract` parent. - Inspecting annotations in a processor → cast to `AnnotatedBeanDefinition` and read `getMetadata()`.
- Why can classpath scanning read annotations without loading the class?ScannedGenericBeanDefinition uses ASM-based AnnotationMetadata read from the .class bytecode. This lets scanners apply include/exclude filters (e.g., @Component presence) before actually class-loading, saving memory and enabling conditional exclusion.
- Which definition type does the container actually instantiate a bean from?A merged RootBeanDefinition. Even for child or generic definitions, getMergedBeanDefinition produces a fully-resolved RootBeanDefinition, and creation happens from that flattened copy.
saying these in an interview costs you the question
- Saying you should register RootBeanDefinition directly for normal user beans (use GenericBeanDefinition).
- Claiming ChildBeanDefinition is the recommended modern approach (it's legacy; use GenericBeanDefinition.setParentName).
- Thinking scanning loads every candidate class (it reads metadata via ASM first).