Foundation · Understand
Bean Definitions & Metadata
Component scanning produces BeanDefinition metadata first and instantiates objects later; the plan precedes the instances.
Primary source: Spring Framework 6.2 Reference: Bean Overview & Definitions
Lesson 4 of 14
1d streakThe Architectural Question
Assuming scanning equals instantiation hides why lazy init, factory beans, and post-processors can reshape the object graph before anything is constructed.
What this guide proves
- BeanDefinition
- Metadata
- Class Scanning
A BeanDefinition is a metadata recipe, not a Java instance
Before Spring creates a single object instance, it constructs an in-memory metadata catalog describing how every bean should be built. This metadata blueprint is defined by the org.springframework.beans.factory.config.BeanDefinition interface.
Architectural Boundary · Do Not Confuse
Java Class Reflection (java.lang.Class)
Spring BeanDefinition (Container Recipe)
A java.lang.Class describes constructors, methods, and fields known to the JVM compiler. A Spring BeanDefinition describes container-specific lifecycle policies: scope (singleton/prototype), lazy-initialization, constructor argument references, setter property values, primary status, factory method names, and init/destroy callbacks.
| BeanDefinition Property | Setter / Getter Method | Architectural Role |
|---|---|---|
| beanClassName | getBeanClassName() / setBeanClassName() | Fully qualified class name of the target bean to instantiate |
| scope | getScope() / setScope() | Defines instance reuse (singleton, prototype, request, session) |
| lazyInit | isLazyInit() / setLazyInit() | Controls whether the bean is created at boot or deferred to first lookup |
| primary | isPrimary() / setPrimary() | Determines if this bean wins ambiguous @Autowired candidate resolution |
| factoryBeanName | getFactoryBeanName() / setFactoryBeanName() | Names the @Configuration class that contains the @Bean factory method |
| factoryMethodName | getFactoryMethodName() / setFactoryMethodName() | Names the @Bean factory method that produces this bean |
| constructorArgumentValues | getConstructorArgumentValues() | Holds indexed and generic constructor argument values and bean references |
| propertyValues | getPropertyValues() | MutablePropertyValues containing property names and values for setter injection |
| initMethodName | getInitMethodName() / setInitMethodName() | Custom initialization method name (e.g. initMethod in @Bean) |
| role | getRole() / setRole() | ROLE_APPLICATION (user code) vs ROLE_INFRASTRUCTURE (Spring internals) |
The two-phase container architecture: Definition vs instantiation
Spring separates container execution into two distinct, sequential phases: the Metadata Registration Phase and the Bean Instantiation Phase. This two-phase separation is the foundational secret behind Spring Boot auto-configuration and environment interpolation.
- 01PHASE 1 (Metadata Registration): Readers and scanners parse XML, @Component classes, and @Configuration classes. They populate the BeanDefinitionRegistry with BeanDefinition objects. No user bean constructors run during this phase.
- 02PHASE 1.5 (Post-Processing Metadata): BeanFactoryPostProcessor implementations execute. For example, PropertySourcesPlaceholderConfigurer inspects BeanDefinition property values and resolves "${db.url}" from application.properties before any database connection is created.
- 03PHASE 2 (Instance Creation & Wiring): During finishBeanFactoryInitialization(), the container iterates over registered BeanDefinition recipes, instantiates real Java objects, injects collaborators, runs BeanPostProcessors, and caches singletons in DefaultSingletonBeanRegistry.
Why the two-phase separation is genius
How XML, annotations, and @Bean converge in the registry
Regardless of how a developer declares a component, Spring uses specialized readers to convert that input into standard BeanDefinition implementations stored in the BeanDefinitionRegistry (which is implemented directly by DefaultListableBeanFactory):
| Configuration Source | Underlying Parser / Scanner | Generated BeanDefinition Type |
|---|---|---|
| XML <bean id="..." class="..."> | XmlBeanDefinitionReader | GenericBeanDefinition |
| @Component / @Service / @Repository | ClassPathBeanDefinitionScanner | ScannedGenericBeanDefinition |
| @Configuration with @Bean method | ConfigurationClassBeanDefinitionReader | ConfigurationClassBeanDefinition |
| Programmatic Registration | BeanDefinitionBuilder | RootBeanDefinition or GenericBeanDefinition |
The BeanDefinitionRegistry maintains an internal Map<String, BeanDefinition> beanDefinitionMap and an ordered List<String> beanDefinitionNames. Once registered, the container processes every definition uniformly, regardless of whether it originated from XML, annotations, or Java code.
Programmatic registration with BeanDefinitionBuilder
Spring Boot auto-configuration classes frequently register beans programmatically rather than relying on static annotations. You can programmatically construct and register a BeanDefinition directly using BeanDefinitionBuilder:
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.GenericApplicationContext;
public class ProgrammaticBeanRegistration {
public static void main(String[] args) {
var context = new GenericApplicationContext();
// 1. Programmatically define a repository bean definition
BeanDefinition repoDef = BeanDefinitionBuilder
.genericBeanDefinition(InMemoryTaskRepository.class)
.setScope(BeanDefinition.SCOPE_SINGLETON)
.getBeanDefinition();
// 2. Programmatically define a service bean definition with constructor reference
BeanDefinition serviceDef = BeanDefinitionBuilder
.genericBeanDefinition(TaskService.class)
.setScope(BeanDefinition.SCOPE_SINGLETON)
.addConstructorArgReference("taskRepository") // Injects bean named 'taskRepository'
.getBeanDefinition();
// 3. Register definitions into the BeanDefinitionRegistry
context.registerBeanDefinition("taskRepository", repoDef);
context.registerBeanDefinition("taskService", serviceDef);
// 4. Refresh the context: triggers Phase 2 (Instantiation & Injection)
context.refresh();
TaskService service = context.getBean(TaskService.class);
System.out.println("Service running with container-wired repository: " + service);
}
}This demonstrates that Spring Framework annotations like @Service and @Bean are not magical language extensions—they are simply declarative conveniences that generate BeanDefinition recipes behind the scenes.
Break it deliberately: The BeanDefinitionOverrideException
In legacy Spring applications, registering two bean definitions with the exact same name resulted in silent bean overriding: the last definition processed would silently overwrite the previous one. This caused subtle, catastrophic production bugs when a library or starter unintentionally replaced a core application service.
Starting in Spring Boot 2.1+, bean definition overriding is disabled by default (spring.main.allow-bean-definition-overriding=false). Attempting to register two beans with identical names immediately halts startup with a fatal diagnostic exception.
@Configuration
public class PrimaryConfig {
@Bean("taskRepository")
public TaskRepository repoA() { return new InMemoryTaskRepository(); }
}
@Configuration
public class SecondaryConfig {
@Bean("taskRepository") // Same bean name!
public TaskRepository repoB() { return new DatabaseTaskRepository(); }
}APPLICATION FAILED TO START *************************** Description: The bean 'taskRepository', defined in class path resource [SecondaryConfig.class], could not be registered. A bean with that name has already been defined in class path resource [PrimaryConfig.class] and overriding is disabled. Action: Consider renaming one of the beans or being more specific about which bean should take precedence: - Rename one of the @Bean methods. - Or use @ConditionalOnMissingBean to allow consumer overrides.
How to resolve definition collisions
Check your understanding
Active Recall Checkpoint
At what point in the Spring container lifecycle do BeanFactoryPostProcessors execute, and what data structures do they operate upon?
Hint: Remember the distinction between Phase 1 (metadata) and Phase 2 (instances).
Show answer & explanation
BeanFactoryPostProcessors execute during Phase 1 (Metadata Registration), after all BeanDefinitions have been loaded into the BeanDefinitionRegistry, but BEFORE any singleton bean instances are constructed. They inspect and mutate BeanDefinition properties (e.g. resolving property placeholders).
Active Recall Checkpoint
Why did Spring Boot disable bean definition overriding by default, and what exception is thrown when a collision occurs?
Hint: Think about accidental shadowing of beans across independent libraries.
Show answer & explanation
Spring Boot disabled overriding by default to prevent silent bean shadowing, where an imported configuration or library inadvertently replaced an application bean without warning. Collisions trigger BeanDefinitionOverrideException at startup.
Runtime Architecture & Flow
Structure under inspection
Bean Definitions & Metadata
Part 1
BeanDefinition
Part 2
Metadata
Part 3
Class Scanning
The connectors show containment and responsibility—not time or call order.
Working Implementation
Code Walkthrough
Inject `ListableBeanFactory` and print the names of all registered BeanDefinitions.
package com.example.core;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
public class BeanDefinitionInspectorLab {
public static class AuditService {
private final String channel;
public AuditService(String channel) { this.channel = channel; }
public String getChannel() { return channel; }
}
public static void main(String[] args) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 1. Build BeanDefinition metadata recipe before creating any live object
BeanDefinition definition = BeanDefinitionBuilder
.genericBeanDefinition(AuditService.class)
.addConstructorArgValue("security-audit-kafka")
.setScope(BeanDefinition.SCOPE_SINGLETON)
.setLazyInit(false)
.setPrimary(true)
.getBeanDefinition();
// 2. Register metadata recipe into the BeanDefinitionRegistry
factory.registerBeanDefinition("auditService", definition);
// 3. Inspect metadata before instantiation
BeanDefinition inspected = factory.getBeanDefinition("auditService");
System.out.println("Class metadata: " + inspected.getBeanClassName());
System.out.println("Scope metadata: " + inspected.getScope());
System.out.println("Lazy init flag: " + inspected.isLazyInit());
// 4. Instantiation occurs when requested or during eager pre-instantiation
AuditService instance = factory.getBean(AuditService.class);
System.out.println("Live bean instantiated: channel=" + instance.getChannel());
}
}Synthesis & Production Verification
Production Acceptance Criteria
- List registered bean definition names via ListableBeanFactory
- Distinguish a BeanDefinition from a live bean instance
- Trigger lazy instantiation and log exactly when it happens
Key Takeaway
A BeanDefinition is a metadata recipe that decouples object description from object creation. Spring completes metadata registration and allows BeanFactoryPostProcessors to modify recipes before constructing any bean instances.
Architectural & Technical Review
Core Interview Questions
Q01Why does Spring separate the container lifecycle into a Metadata Registration Phase and an Instantiation Phase?Show answer
Separating metadata registration from instantiation allows the container to collect all bean recipes into the BeanDefinitionRegistry, inspect and modify them via BeanFactoryPostProcessors (such as resolving property placeholders), and evaluate conditional rules (@ConditionalOnMissingBean) before allocating memory or running constructors.
Q02What core properties are stored inside a BeanDefinition object in the Spring IoC container?Show answer
A BeanDefinition stores the target bean class name, scope (singleton, prototype, request), lazy-init flag, primary designation, constructor argument values and bean references, property values for setter injection, factory bean and factory method names, and init/destroy lifecycle callbacks.
Q03Why does Spring Boot disable bean definition overriding by default, and how is a collision resolved?Show answer
Spring Boot disables overriding to prevent silent bean shadowing, where an auto-configuration or library inadvertently overwrites a core application bean. When duplicate bean names collide, it throws BeanDefinitionOverrideException. Resolution involves renaming the bean, using @Primary, or applying @ConditionalOnMissingBean.
Production Pitfalls
Critical Anti-Patterns
- Pitfall: Re-enabling bean definition overriding in production — Setting spring.main.allow-bean-definition-overriding=true masks configuration mistakes and introduces order-dependent bean shadowing bugs that are notoriously difficult to trace.
- Pitfall: Performing expensive business logic in BeanFactoryPostProcessors — BeanFactoryPostProcessors run during Phase 1 when beans are not yet created. Instantiating business beans prematurely inside a BFPP can trigger early initialization bugs and bypass BeanPostProcessors.
Primary Documentation & Specifications
Lesson Primary References
Supplementary Module Resources