skip to content

What is the difference between postProcessBeforeInitialization and postProcessAfterInitialization, and why do proxies get created in the 'after' method?

level: middleimportance: should knowfreq 58%

answer

  1. before → init callbacks → after
  2. Wrap in AFTER so target is fully initialized
  3. after result = cached & injected everywhere
  4. AbstractAutoProxyCreator, AsyncAnnotationBeanPostProcessor
  5. Self-invocation bypasses the proxy

basics

~10 s

'Before' runs just ahead of a bean's init callbacks; 'after' runs just after them. Proxies are created in 'after' so the wrapped object already had its @PostConstruct and afterPropertiesSet run on the real target.

solid answer

~40 s

Both take the bean and name and return a bean. postProcessBeforeInitialization fires immediately before the initialization callbacks (@PostConstruct → InitializingBean.afterPropertiesSet → custom init-method); postProcessAfterInitialization fires immediately after them. Frameworks build proxies in the 'after' method for two reasons. First, initialization must run on the real target, not the proxy — you want @PostConstruct to execute on your actual object before it gets wrapped. Second, 'after' is the last thing the container does, so its return value is what gets cached in the singleton pool and injected into every other bean; returning a proxy there guarantees all consumers receive the proxy. Spring's AOP infrastructure (AbstractAutoProxyCreator) and @Async (AsyncAnnotationBeanPostProcessor) both override postProcessAfterInitialization to wrap eligible beans.

code

java · 21 lines
java
// Sketch of how an auto-proxy BPP swaps the bean in the AFTER method.
import org.springframework.beans.factory.config.BeanPostProcessor;

public class WrappingBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof PaymentService) {
            // real target already had @PostConstruct/afterPropertiesSet run;
            // now return a proxy that consumers will receive instead.
            return java.lang.reflect.Proxy.newProxyInstance(
                bean.getClass().getClassLoader(),
                bean.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    // cross-cutting logic here, then delegate
                    return method.invoke(bean, args);
                });
        }
        return bean;
    }
}

go deeper

for a junior

Just knowing before/after bracket the init phase is enough.

for a middle

Explain why 'after' is chosen for proxying and name the two reasons (target initialized, return value is authoritative).

for a senior

Add early-reference proxying for circular deps and self-invocation implications.

for a principal

Discuss BPP ordering, nested proxies, and getEarlyBeanReference consistency guarantees.

## The two methods, precisely Both signatures are `Object m(Object bean, String beanName)`. The **difference is timing** relative to the bean's *initialization callbacks*, which are, in order: 1. `@PostConstruct`-annotated method (handled by `CommonAnnotationBeanPostProcessor`). 2. `InitializingBean.afterPropertiesSet()`. 3. A custom `init-method` (XML `init-method` or `@Bean(initMethod=...)`). `postProcessBeforeInitialization` runs **before step 1**; `postProcessAfterInitialization` runs **after step 3**. Everything about DI is already done by the time either runs. ## Why proxies are created in 'after', not 'before' A proxy wraps a **target**. You want that target to be a fully-initialized object: - If you wrapped in `before`, the container would then run `@PostConstruct`/`afterPropertiesSet` **on the proxy**, not cleanly on your real object — and a proxy may not even expose those lifecycle methods the way you expect. Initialization semantics would be muddled. - By wrapping in `after`, initialization has already executed on the genuine target instance; the proxy simply delegates future method calls to that ready object. Equally important is **who sees the result**. `postProcessAfterInitialization` is the final transformation the container applies. Its return value becomes the object stored in the singleton cache (`DefaultSingletonBeanRegistry`) and therefore the object injected into every other bean and returned by `getBean`. Returning the proxy here makes the swap *authoritative and global*. ## Concrete framework examples - **Spring AOP**: `AbstractAutoProxyCreator` (e.g. `AnnotationAwareAspectJAutoProxyCreator`) is a BPP. In `postProcessAfterInitialization` it checks whether the bean matches any advisor (pointcut); if so it returns a proxy (JDK dynamic proxy or CGLIB) instead of the raw bean. (The *weaving* mechanics themselves are a separate topic.) - **`@Async`**: `AsyncAnnotationBeanPostProcessor` wraps beans with `@Async` methods so calls run on a `TaskExecutor`. - **`@Transactional`, caching, `@Configuration`** enhancement — all rely on BPPs producing proxies in the same phase. ## Edge cases and gotchas - **Early references / circular deps**: if bean A needs proxying but is referenced by B while A is still being created, Spring may proxy it early via `getEarlyBeanReference` (an `SmartInstantiationAwareBeanPostProcessor` hook) so that both the injected reference and the final bean are the *same* proxy. Without this, you could get a raw target injected somewhere and a proxy elsewhere. - **Self-invocation**: because the proxy is created around the target, a bean calling `this.someAsyncMethod()` bypasses the proxy — the advice (async/transaction) does not apply. This surprises many candidates. - **`@PostConstruct` sees the raw object**: lifecycle callbacks run on the target, so `this` inside them is not the proxy. - **Ordering across BPPs**: if multiple BPPs create proxies, `Ordered`/`@Order` determines who wraps whom (proxy-of-proxy layering).

  • Why does self-invocation break @Async/@Transactional given how proxies are created?
    The proxy wraps the target and intercepts external calls. Inside the bean, `this` is the raw target, not the proxy, so `this.method()` skips the interceptor and the advice never runs.
  • If two BeanPostProcessors both want to proxy the same bean, what controls the outcome?
    Their order (Ordered interface or @Order). The lower-order BPP runs first; a later BPP may wrap the already-created proxy, producing nested proxies.

saying these in an interview costs you the question

  • Claiming proxies are created in postProcessBeforeInitialization
  • Saying @PostConstruct runs on the proxy rather than the target
  • Thinking self-invocation still triggers async/transactional advice

context