A BeanPostProcessor (BPP) is a container extension point that lets you intercept every bean instance right around its initialization callbacks — and it's the machinery behind @Autowired, @Transactional, and AOP. If you understand BPP, you understand how "magic" annotations actually work.
The interface
Two callbacks, both invoked by the container for each bean:
public interface BeanPostProcessor {
default Object postProcessBeforeInitialization(Object bean, String name) { return bean; }
default Object postProcessAfterInitialization(Object bean, String name) { return bean; }
}
The key superpower: each method returns an object, and the container uses the returned reference going forward. So a BPP can replace a bean with a proxy that wraps it. That return-and-replace is exactly how Spring creates transactional and AOP proxies.
When it runs
Per bean, after instantiation and property population:
postProcessBeforeInitializationruns before@PostConstruct/afterPropertiesSet()/ init-method.postProcessAfterInitializationruns after all init callbacks — this is where AOP/transaction proxies are typically created and returned.
It operates on fully instantiated instances, which is the defining contrast with BeanFactoryPostProcessor (which works on bean definitions before any instance exists).
Real-world examples (Spring's own)
These aren't toy examples — they're how core features are implemented:
AutowiredAnnotationBeanPostProcessor— implements@Autowiredand@Valueinjection.CommonAnnotationBeanPostProcessor— implements@PostConstruct,@PreDestroy,@Resource.AnnotationAwareAspectJAutoProxyCreator— wraps beans in AOP/@Transactionalproxies.
@Component
public class TimingBpp implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String name) {
if (bean instanceof ExpensiveService) {
return Proxy.newProxyInstance(/* ...wrap with timing... */);
}
return bean; // pass others through untouched
}
}