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

35 min

Lesson 11 of 14

1d streak

The 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

Concept A

BeanFactoryPostProcessor (Phase 1: Metadata)

Concept B

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.

DimensionBeanFactoryPostProcessor (BFPP)BeanPostProcessor (BPP)
Lifecycle PhasePhase 1: Metadata RegistrationPhase 2: Bean Instantiation & Wiring
Input ArgumentConfigurableListableBeanFactory (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 ExamplesPropertySourcesPlaceholderConfigurer, ConfigurationClassPostProcessorAutowiredAnnotationBeanPostProcessor, 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:

AuditingBeanPostProcessor.java
@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!

Terminal / Diagnostic Output
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

Because billingService was instantiated prematurely during Phase 1, it completely misses the AnnotationAwareAspectJAutoProxyCreator. Its @Transactional and @Cacheable annotations are SILENTLY IGNORED at runtime!

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.

CustomPostProcessorLab.java
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;
    }
}
BeanFactoryPostProcessor operates on BeanDefinition recipes before any beans exist.
BeanPostProcessor operates on live bean instances around the initialization phase.
Spring AOP (@Transactional, @Async, @Cacheable) attaches behavior inside postProcessAfterInitialization.
A replacement proxy must preserve the contracts callers inject, which is why proxy type and interface visibility matter.

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

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

Supplementary Module Resources