BeanPostProcessor — How Spring Modifies Beans Before Use
Fast recall
Quick revision
Core answer
@PostConstruct / afterPropertiesSet boundary. The Before hook runs on the raw bean before init callbacks; the After hook runs after init callbacks and is the hook that powers AOP proxying — this is where @Transactional, @Async, @Cacheable, and custom aspects are applied. Spring Boot's auto-configuration uses dozens of built-in BeanPostProcessors (AutowiredAnnotationBeanPostProcessor wires @Autowired, CommonAnnotationBeanPostProcessor wires @PostConstruct/@Resource, AnnotationAwareAspectJAutoProxyCreator creates AOP proxies). Order matters: register via @Order or implement Ordered; BPPs are instantiated very early, so they cannot depend on other @Beans that haven't been created yet.Whether you understand BeanPostProcessor is the extension point behind AOP and that it runs on every bean, not just specific ones
Confusing BeanPostProcessor (modifies beans) with BeanFactoryPostProcessor (modifies bean definitions); thinking it's only called on beans you annotate
Explain that postProcessAfterInitialization is where AOP proxies are created (return a proxy wrapping the bean); mention the ordering problem when a BPP depends on regular @Bean definitions
Interview-ready guide
Interview answer
BeanPostProcessor is the most powerful internal interface in Spring — every annotation-driven feature you use daily is implemented as one.
What it is
- An interface with two methods:
postProcessBeforeInitialization(before@PostConstruct) andpostProcessAfterInitialization(after) - Every annotation-driven Spring feature is a BPP:
@Autowiredinjection,@Transactionalproxying,@Asyncwrapping,@Scheduledregistration
The proxy trick in `postProcessAfterInitialization`
- This method can return a completely different object — typically a CGLIB proxy wrapping the original bean
- Spring registers whatever this method returns in the container; all other beans inject the proxy, not the original
- This is why
@Transactionalworks — and why calling a@Transactionalmethod viathisbypasses it:thisis the original, not the proxy
BPP vs `BeanFactoryPostProcessor`
BeanPostProcessormodifies bean instances after they are createdBeanFactoryPostProcessormodifies bean definitions before any bean is created —PropertySourcesPlaceholderConfigurer(which resolves${...}placeholders) is aBeanFactoryPostProcessor
Knowing that proxy creation happens in postProcessAfterInitialization explains most of the why does my annotation not work debugging sessions — from @Transactional self-invocation to @Async on the same class.
BeanPostProcessor — How Spring Modifies Beans Before Use
Direct Answer
A BeanPostProcessor is a Spring extension point with two hooks — postProcessBeforeInitialization and postProcessAfterInitialization — that the container calls on every bean during creation, right around the @PostConstruct / afterPropertiesSet boundary. The Before hook runs on the raw bean before init callbacks; the After hook runs after init callbacks and is the hook that powers AOP proxying — this is where @Transactional, @Async, @Cacheable, and custom aspects are applied. Spring Boot's auto-configuration uses dozens of built-in BeanPostProcessors (AutowiredAnnotationBeanPostProcessor wires @Autowired, CommonAnnotationBeanPostProcessor wires @PostConstruct/@Resource, AnnotationAwareAspectJAutoProxyCreator creates AOP proxies). Order matters: register via @Order or implement Ordered; BPPs are instantiated very early, so they cannot depend on other @Beans that haven't been created yet.
Code Example
The most practical way to understand BeanPostProcessor is to build one. The example creates a custom @Timed annotation that automatically logs how long each method takes — similar to Micrometer or Spring AOP, but implemented from scratch to show the mechanism.
The processor scans every bean for methods annotated with @Timed. When it finds one, it wraps the original bean in a CGLIB proxy that intercepts method calls and measures execution time. This is the same pattern Spring uses internally for @Transactional and @Cacheable.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Timed {}
@Component
public class TimingPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String name) {
boolean hasTimed = Arrays.stream(bean.getClass().getMethods())
.anyMatch(m -> m.isAnnotationPresent(Timed.class));
if (!hasTimed) return bean;
return Enhancer.create(bean.getClass(), (MethodInterceptor) (obj, method, args, proxy) -> {
if (!method.isAnnotationPresent(Timed.class))
return proxy.invokeSuper(obj, args);
long start = System.nanoTime();
Object result = proxy.invokeSuper(obj, args);
log.info("{}.{} took {}ms", name, method.getName(),
(System.nanoTime() - start) / 1_000_000);
return result;
});
}
}
The key insight is the return value. The postProcessAfterInitialization method returns either the original bean (no processing needed) or a new object (the proxy). Spring replaces the bean in the container with whatever this method returns. This is why @Transactional works — the bean registered in the container is a proxy, not the original class.
Practical performance note
- Proxy creation happens once at startup for singleton beans — no per-call cost
- CGLIB proxies add roughly 1-2 microseconds per method call — negligible for most applications
- For post-processors that wrap thousands of beans, the startup time impact can be measurable; profile before optimizing
Common Mistakes & Pitfalls
Confusing BeanPostProcessor (modifies beans) with BeanFactoryPostProcessor (modifies bean definitions); thinking it's only called on beans you annotate
Check your understanding
Answer these without looking back at the article.
- How does BeanPostProcessor differ from BeanFactoryPostProcessor?
- Why does @Transactional on a self-invoked method not work?
- What ordering problem arises when a BeanPostProcessor depends on an @Autowired bean?
- How does AnnotationAwareAspectJAutoProxyCreator decide which beans to proxy?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.