Foundation · Understand
BeanPostProcessor & BeanFactoryPostProcessor
Understand the extension points that inspect bean definitions, alter metadata, and wrap finished beans before application code receives them.
Primary source: Spring Framework 6.2 Reference: Customizing Beans Using a BeanPostProcessor
Lesson 11 of 14
1d streakThe Architectural Question
@Transactional, @Autowired, and @Value have no effect by themselves; post-processor invocations are where that behavior actually gets attached to your beans.
What this guide proves
- Separate definition-time changes from instance-time wrapping
- Recognize proxy creation as post-processing
- Avoid business logic inside container extension points
BeanFactoryPostProcessor vs BeanPostProcessor: Phase 1 vs Phase 2
Spring provides two master extension points for participating in container bootstrap: BeanFactoryPostProcessor and BeanPostProcessor. Confusing these two interfaces is a primary source of subtle startup defects.
Architectural Boundary · Do Not Confuse
BeanFactoryPostProcessor (Phase 1: Metadata)
BeanPostProcessor (Phase 2: Instances)
A BeanFactoryPostProcessor normally operates on BeanDefinition metadata before ordinary application singletons are created. Infrastructure needed to register post-processors may already have been instantiated, and calling getBean() from a post-processor can trigger early creation. It can inspect and modify metadata such as property placeholders. A BeanPostProcessor operates on real instantiated Java objects, executing around initialization to validate, wrap, or proxy them.
| Dimension | BeanFactoryPostProcessor (BFPP) | BeanPostProcessor (BPP) |
|---|---|---|
| Lifecycle Phase | Phase 1: Metadata Registration | Phase 2: Bean Instantiation & Wiring |
| Input Argument | ConfigurableListableBeanFactory (BeanDefinitions) | Object bean, String beanName (Live instances) |
| Can alter bean metadata? | YES (modifies class name, properties, scopes) | NO (metadata is already frozen) |
| Can wrap bean in AOP Proxy? | NO (instances do not exist yet) | YES (postProcessAfterInitialization generates proxies) |
| Famous Examples | PropertySourcesPlaceholderConfigurer, ConfigurationClassPostProcessor | AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, AnnotationAwareAspectJAutoProxyCreator |
Building a custom BeanPostProcessor: Metrics & auditing
You can create custom BeanPostProcessors to inject custom cross-cutting behavior. For example, a BPP that intercepts beans annotated with a custom @Audited marker and wraps them in a logging proxy:
@Component
public class AuditingBeanPostProcessor implements BeanPostProcessor {
private static final Logger log = LoggerFactory.getLogger(AuditingBeanPostProcessor.class);
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// Inspect bean before @PostConstruct
if (bean.getClass().isAnnotationPresent(Audited.class)) {
log.info("Auditing bean registered before init: {}", beanName);
}
return bean; // Must return the bean (or a wrapper)!
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// Called after initialization: can wrap with dynamic proxy
return bean;
}
}Break it deliberately: Early initialization in BeanFactoryPostProcessor
Calling beanFactory.getBean("myService") inside a BeanFactoryPostProcessor forces Spring to instantiate that bean prematurely during Phase 1—BEFORE the BeanPostProcessor chain is even registered!
WARN org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker: Bean 'billingService' of type [com.example.billing.BillingService] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)!
The Catastrophic Consequence
Check your understanding
Active Recall Checkpoint
Why does calling getBean() inside a BeanFactoryPostProcessor cause that bean to lose transactional and security proxy capabilities?
Hint: Think about when auto-proxy creators are registered.
Show answer & explanation
Auto-proxy creators are BeanPostProcessors registered in Phase 2. Instantiating a bean during Phase 1 inside a BeanFactoryPostProcessor creates the object before proxy creators exist, so the bean is never proxied and annotations like @Transactional silently fail.
Active Recall Checkpoint
What must a BeanPostProcessor method return from postProcessBeforeInitialization or postProcessAfterInitialization?
Hint: Can it return null?
Show answer & explanation
It must return either the original bean instance or a replacement/proxy object. Returning null causes the container to discard the bean from further post-processing.
Runtime Architecture & Flow
Application startup · runtime visualization
BeanPostProcessor & BeanFactoryPostProcessor
Environment
Separate definition-time changes from instance-time wrapping
Conditions
Recognize proxy creation as post-processing
Bean graph
Avoid business logic inside container extension points
Runtime
Write a BeanPostProcessor that logs proxy candidates and a BeanFactoryPostProcessor that verifies required bean definitions.
Working Implementation
Code Walkthrough
Write a BeanPostProcessor that logs proxy candidates and a BeanFactoryPostProcessor that verifies required bean definitions.
package com.example.core;
import java.lang.reflect.Proxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class CustomPostProcessorLab implements BeanPostProcessor {
private static final Logger log = LoggerFactory.getLogger(CustomPostProcessorLab.class);
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// Runs BEFORE @PostConstruct and InitializingBean: inspect or configure initial properties
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// Runs AFTER initialization: this is where Spring wraps beans in dynamic proxies
if (beanName.endsWith("Service")) {
log.info("Inspecting bean '{}' for cross-cutting proxy wrapping", beanName);
}
// Dynamic proxy creation for interface-backed services requiring timing instrumentation
if (bean instanceof TimedService timed) {
return Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
new TimingInvocationHandler(timed));
}
return bean;
}
}Synthesis & Production Verification
Production Acceptance Criteria
- Log a BeanFactoryPostProcessor pass over bean definitions before any instantiation
- Wrap one selected bean in a proxy from a BeanPostProcessor
- Explain why @PostConstruct fires from inside post-processing rather than before it
Key Takeaway
BeanFactoryPostProcessors modify BeanDefinition metadata in Phase 1; BeanPostProcessors intercept and wrap live bean instances in Phase 2.
Architectural & Technical Review
Core Interview Questions
Q01What is the core architectural difference between a BeanFactoryPostProcessor and a BeanPostProcessor?Show answer
A BeanFactoryPostProcessor operates on BeanDefinition metadata in Phase 1 before any instances exist. A BeanPostProcessor operates on real instantiated Java objects in Phase 2 around initialization.
Q02Why does calling beanFactory.getBean() inside a BeanFactoryPostProcessor cause early initialization warnings and broken proxies?Show answer
Instantiating a bean during Phase 1 creates the object before BeanPostProcessors (like AnnotationAwareAspectJAutoProxyCreator) are registered, causing the bean to permanently miss @Transactional and @Cacheable proxies.
Q03How do auto-proxy creators use the BeanPostProcessor postProcessAfterInitialization hook to enable declarative transactions?Show answer
The post-processor inspects the bean for transactional annotations, creates a CGLIB or JDK dynamic proxy wrapping the target, and returns the proxy to the container to be cached in the singleton registry.
Production Pitfalls
Critical Anti-Patterns
- Pitfall: Invoking getBean() inside a BeanFactoryPostProcessor — Calling getBean() prematurely forces bean instantiation before post-processors are registered, causing target beans to permanently lose AOP proxy features.
- Pitfall: Returning null from a BeanPostProcessor callback — Returning null from postProcessBeforeInitialization or postProcessAfterInitialization causes the container to discard the bean from the registry.
Primary Documentation & Specifications
Lesson Primary References
Supplementary Module Resources