BeanFactoryPostProcessor and BeanPostProcessor: The Two Extension Points That Drive Everything Else
The Two Extension Points
Spring’s container is not a static box. It is a pipeline with precisely two extension points that third-party code (and Spring itself) can plug into. Every Spring feature you use, from @Autowired injection to @Transactional proxies to property placeholder resolution, is implemented through one of these two interfaces:
org.springframework.beans.factory.config.BeanFactoryPostProcessoroperates on bean definitions (metadata). It fires before any bean instance exists.org.springframework.beans.factory.config.BeanPostProcessoroperates on bean instances. It fires as each bean is created.
This is the single most important distinction in the Spring container. Confusing the two causes bugs that are difficult to diagnose because they manifest as missing dependencies, unproxied beans, or silent misconfiguration.
Where They Fire in the Lifecycle
Recall from CH2 that AbstractApplicationContext.refresh() executes a defined sequence of steps. The two extension points occupy different positions in that sequence:
refresh() {
// Step 1: prepareRefresh()
// Step 2: obtainFreshBeanFactory()
// Step 3: prepareBeanFactory()
// Step 4: postProcessBeanFactory() // Template method for subclasses
// Step 5: invokeBeanFactoryPostProcessors() // <-- BFPP fires here
// Step 6: registerBeanPostProcessors() // BPP instances are registered (not invoked)
// ...
// Step 11: finishBeanFactoryInitialization() // <-- BPP fires here (per bean)
// Step 12: finishRefresh()
}
Step 5 calls org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(). This method instantiates all BeanFactoryPostProcessor beans and invokes them. At this point, the BeanFactory contains BeanDefinition objects but no application beans. The BFPPs read, modify, or add definitions.
Step 6 instantiates all BeanPostProcessor beans and registers them with the factory. They do not fire yet.
Step 11 calls DefaultListableBeanFactory.preInstantiateSingletons(), which iterates through every non-lazy singleton BeanDefinition and calls getBean(). For each bean, the creation pipeline invokes every registered BeanPostProcessor at two points: before initialization (postProcessBeforeInitialization) and after initialization (postProcessAfterInitialization).
BeanFactoryPostProcessor: Modifying the Blueprint
A BeanFactoryPostProcessor receives the entire ConfigurableListableBeanFactory and can do anything with it: read definitions, modify definitions, register new definitions.
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException;
}
The most important BFPP in Spring is org.springframework.context.annotation.ConfigurationClassPostProcessor. It implements the specialized sub-interface BeanDefinitionRegistryPostProcessor, which adds a method that fires even earlier:
public interface BeanDefinitionRegistryPostProcessor
extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException;
}
ConfigurationClassPostProcessor is responsible for processing @Configuration, @Component, @Import, and @ComponentScan annotations. It reads those annotations from existing BeanDefinition metadata, discovers new classes, and registers their BeanDefinition objects into the registry. Without this single class, annotation-based configuration would not exist.
Another critical BFPP is org.springframework.context.support.PropertySourcesPlaceholderConfigurer. It resolves ${...} placeholders in bean definition property values by reading from PropertySource instances. It modifies BeanDefinition metadata, replacing placeholder strings with resolved values before any bean is instantiated.
What You Can Do in a BFPP
- Change a bean’s scope from
singletontoprototype - Add or remove property values on a
BeanDefinition - Register entirely new
BeanDefinitionobjects - Remove bean definitions from the registry
- Read configuration from external sources and translate it into bean definitions
What You Cannot Do in a BFPP
You cannot safely use @Autowired or depend on other application beans. When BFPPs execute, those beans do not exist yet. If your BFPP forces early instantiation of a bean (by calling beanFactory.getBean()), that bean will miss processing by other BFPPs that have not yet run. This is a source of subtle bugs.
BeanPostProcessor: Modifying the Instance
A BeanPostProcessor intercepts each bean as it is created. It receives the raw bean instance and its name, and returns either the same instance or a replacement (typically a proxy).
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
}
The creation pipeline for a single bean looks like this:
1. Instantiate (constructor call)
2. Populate properties (dependency injection via setters/fields)
3. BeanPostProcessor.postProcessBeforeInitialization() // for ALL registered BPPs
4. Initialize (@PostConstruct, InitializingBean.afterPropertiesSet(), init-method)
5. BeanPostProcessor.postProcessAfterInitialization() // for ALL registered BPPs
Spring’s own features are implemented as BPPs:
| BeanPostProcessor | What It Does |
|---|---|
AutowiredAnnotationBeanPostProcessor | Processes @Autowired and @Value injection |
CommonAnnotationBeanPostProcessor | Processes @PostConstruct, @PreDestroy, @Resource |
AbstractAutoProxyCreator (subclasses) | Creates AOP proxies for @Transactional, @Async, @Cacheable |
ScheduledAnnotationBeanPostProcessor | Processes @Scheduled methods |
AsyncAnnotationBeanPostProcessor | Processes @Async methods |
This is not a plugin system bolted onto Spring. This is Spring. Remove AutowiredAnnotationBeanPostProcessor and @Autowired stops working. Remove CommonAnnotationBeanPostProcessor and @PostConstruct stops firing.
Ordering
When multiple BFPPs or BPPs exist, ordering determines which runs first. Spring uses three mechanisms, checked in this priority:
PriorityOrderedinterface: Highest priority. These run before anyOrderedprocessors.Orderedinterface: Second tier.getOrder()returns an int; lower values run first.@Orderannotation: Equivalent to implementingOrdered.- No ordering: Runs last, in undefined order.
ConfigurationClassPostProcessor implements PriorityOrdered with Ordered.LOWEST_PRECEDENCE because it needs to run early but after any custom BeanDefinitionRegistryPostProcessor that might register @Configuration classes programmatically.
AutowiredAnnotationBeanPostProcessor also implements PriorityOrdered. The ordering ensures that dependency injection is complete before other BPPs (like proxy creators) see the bean.
PostProcessorRegistrationDelegate sorts processors into four groups and invokes them in this order:
PriorityOrderedBFPPs/BPPs (sorted by order value)OrderedBFPPs/BPPs (sorted by order value)- Regular BFPPs/BPPs (no ordering)
- Internal BFPPs/BPPs (framework infrastructure, like
MergedBeanDefinitionPostProcessor)
BeanDefinitionRegistryPostProcessor: The Specialized BFPP
BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor and adds postProcessBeanDefinitionRegistry(). The distinction matters:
postProcessBeanDefinitionRegistry()fires first. It receives aBeanDefinitionRegistryand is specifically for adding or removing bean definitions.postProcessBeanFactory()fires second. It receives the fullConfigurableListableBeanFactoryand is for modifying existing definitions.
ConfigurationClassPostProcessor uses postProcessBeanDefinitionRegistry() to scan @Configuration classes and register their @Bean method definitions. It uses postProcessBeanFactory() to enhance @Configuration classes with CGLIB proxies so that @Bean method calls between methods return the same singleton instance.
The Failure Mode
Here is the most common mistake developers make with these extension points:
// BROKEN: BeanPostProcessor that depends on a service bean
@Component
public class AuditBeanPostProcessor implements BeanPostProcessor {
@Autowired
private AuditService auditService; // This forces early instantiation of AuditService
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
auditService.log("Bean created: " + beanName);
return bean;
}
}
This compiles and may appear to work, but it causes a cascade of problems. BeanPostProcessor beans are instantiated during step 6 of refresh(), before regular beans. When Spring creates AuditBeanPostProcessor, it must also create AuditService and all of its dependencies. Those beans are instantiated before all BPPs are registered, which means they miss processing by AuditBeanPostProcessor itself and by any BPP registered after it.
Spring logs a warning for this:
Bean 'auditService' of type [com.saas.AuditService] is not eligible for getting
processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
If AuditService is @Transactional, it will not get a proxy. Transactions will silently fail.
The Correct Pattern
// CORRECT: BeanPostProcessor uses BeanFactory for lazy resolution
@Component
public class AuditBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// Resolve lazily, after all BPPs are registered
AuditService auditService = beanFactory.getBean(AuditService.class);
auditService.log("Bean created: " + beanName);
return bean;
}
}
By implementing BeanFactoryAware instead of using @Autowired, the BPP avoids forcing early instantiation. The AuditService is resolved lazily during step 11, when postProcessAfterInitialization is called for each regular bean. At that point, AuditService itself will have been fully processed by all BPPs, including proxy creators.
Alternatively, use ObjectProvider<AuditService> as a constructor parameter. Spring resolves ObjectProvider without instantiating the target bean, deferring resolution until .getObject() is called.
Summary
| Aspect | BeanFactoryPostProcessor | BeanPostProcessor |
|---|---|---|
| Operates on | BeanDefinition (metadata) | Bean instance (object) |
| When it fires | Step 5, before any bean exists | Step 11, as each bean is created |
| Can modify | Scope, properties, qualifiers, class | The instance itself (wrap, replace, modify) |
| Can add beans | Yes (via BeanDefinitionRegistryPostProcessor) | No |
| Key Spring impl | ConfigurationClassPostProcessor | AutowiredAnnotationBeanPostProcessor |
| Common mistake | Using @Autowired (beans don’t exist) | Returning null (removes bean from context) |
These two interfaces are the backbone of Spring’s extensibility. Every feature Spring provides, from property resolution to dependency injection to AOP proxies, is implemented through one of them. Understanding where they fire and what they operate on is the foundation for debugging any Spring application.