skip to content

What are the main BeanDefinition implementations (RootBeanDefinition, GenericBeanDefinition, ChildBeanDefinition, AnnotatedBeanDefinition) and when is each used?

level: middleimportance: should knowfreq 45%

answer

  1. Generic = modern all-purpose
  2. Root = runtime merged/resolved
  3. Child = legacy parent-inheritance
  4. Annotated = carries AnnotationMetadata (ASM, no class load)
  5. Scanned vs AnnotatedGeneric vs ConfigurationClass

basics

~10 s

GenericBeanDefinition 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
java
// 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

for a junior

Know GenericBeanDefinition is the one you create and RootBeanDefinition is internal.

for a middle

Distinguish all four, know Generic replaced the Root/Child split, and that Annotated carries AnnotationMetadata.

for a senior

Explain the merged RootBeanDefinition role and ASM-based metadata enabling filter-before-load scanning.

for a principal

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).

context