skip to content

What are the registration, ordering, and early-instantiation pitfalls of BeanPostProcessors?

level: principalimportance: nice to knowfreq 28%

answer

  1. registerBeanPostProcessors runs before normal singletons
  2. PriorityOrdered → Ordered → rest → internal
  3. BPPs aren't proxied / post-processed themselves
  4. BPP deps instantiated early → 'not eligible for all BeanPostProcessors' warning
  5. static @Bean + ObjectProvider/@Lazy to avoid early instantiation

basics

~20 s

BPPs are created early, before normal beans, so any beans a BPP depends on get built early too — potentially before all BPPs are registered, so they may miss post-processing (e.g. not get proxied). Order BPPs with Ordered/PriorityOrdered; BPPs don't post-process each other.

solid answer

~40 s

During refresh, registerBeanPostProcessors instantiates all BeanPostProcessor beans before ordinary beans, in a defined order: PriorityOrdered first, then Ordered, then the rest, with internal (MergedBeanDefinitionPostProcessor) ones last. Two hazards follow. First, BPPs are not themselves post-processed by other BPPs, so a BPP cannot be AOP-proxied. Second, any bean a BPP depends on (directly or transitively) must be instantiated to build the BPP, which can happen before the full set of BPPs is registered — Spring logs the classic 'is not eligible for getting processed by all BeanPostProcessors' warning, meaning that dependency may not get proxied/transactional. Mitigations: keep BPP dependencies minimal, use ObjectProvider/@Lazy or a static @Bean method for the BPP. Ordering between proxying BPPs determines nesting.

code

java · 16 lines
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.ObjectProvider;

@Configuration
public class ProcessorConfig {

    // STATIC so declaring this BPP does not force early instantiation of the
    // whole @Configuration class and its other beans.
    @Bean
    static MyBeanPostProcessor myBeanPostProcessor(ObjectProvider<AuditService> audit) {
        // Lazy lookup via ObjectProvider avoids pulling AuditService in early,
        // which would risk it 'not being eligible for all BeanPostProcessors'.
        return new MyBeanPostProcessor(audit);
    }
}

go deeper

for a junior

Not expected.

for a middle

Awareness that BPPs are created early and ordered is enough.

for a senior

Explain the early-instantiation hazard and the 'not eligible' warning with a mitigation.

for a principal

Cover full registration ordering (PriorityOrdered/Ordered/internal), static-@Bean rationale, proxy nesting order, and BFPP-vs-BPP boundaries.

## How and when BPPs are registered `AbstractApplicationContext.refresh()` calls `registerBeanPostProcessors(beanFactory)` **before** it finishes initializing the remaining singletons (`finishBeanFactoryInitialization`). That method: 1. Finds all bean definitions of type `BeanPostProcessor`. 2. **Instantiates** them eagerly (they must exist to process other beans). 3. Registers them in a **defined order**: those implementing `PriorityOrdered` first, then `Ordered`, then unordered, and finally the internal `MergedBeanDefinitionPostProcessor`s. Within a rank, `@Order`/`Ordered.getOrder()` decides sequence. ## Pitfall 1 — BPPs are not post-processed Because BPPs are created *to do* post-processing, they are **excluded** from being processed by other BPPs. Consequence: **you cannot AOP-proxy a BeanPostProcessor**, add `@Transactional` to it, etc. Advice you put on a BPP silently doesn't apply. ## Pitfall 2 — early instantiation of BPP dependencies To build a BPP, Spring must build everything the BPP **depends on**. If your BPP `@Autowired`s a service (say a `DataSource`, or worse a business bean), that dependency is instantiated **early**, during the BPP-registration phase — potentially **before all BPPs are registered**. Any BPP registered *after* that point never gets a chance to process the early bean. So: - The early bean may **not be proxied** (no AOP, no `@Transactional`, no caching). - Spring emits the well-known `BeanPostProcessorChecker` INFO/WARN: *"Bean '...' of type [...] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)."* This is a subtle correctness bug: transactions or security silently absent on a bean pulled in early by a BPP. ### Mitigations - **Keep BPP dependencies to the absolute minimum**; ideally none. - Inject dependencies **lazily**: `ObjectProvider<T>`, `@Lazy`, or look them up from the `BeanFactory` on demand instead of at construction. - Declare the BPP via a **`static @Bean` method** in a `@Configuration` class. A non-static `@Bean` for a BPP forces the whole `@Configuration` class (and thus its other beans) to be instantiated early — a very common source of the warning. `static` avoids instantiating the config class. - Consider `BeanFactoryPostProcessor` vs `BeanPostProcessor` distinction: BFPPs operate on **bean definitions** (metadata) before any bean is instantiated; they don't touch instances and run even earlier. Don't reach for a BPP when you only need to mutate definitions. ## Pitfall 3 — ordering among proxying BPPs If multiple BPPs create proxies (AOP auto-proxy creator, a custom wrapper, `@Async`), their relative `@Order` determines **who wraps whom**, producing nested proxies. Getting this wrong can change interception order or break `instanceof`/target-class assumptions. ## Pitfall 4 — scope and prototype beans BPPs run for prototype beans on **each** creation, but the container does **not** manage a prototype's full lifecycle afterward (no destruction callbacks). A BPP that allocates resources per prototype can leak. ## Testing/observability - Watch startup logs for the `BeanPostProcessorChecker` message — it is the canonical signal of pitfall 2. - `PriorityOrdered` BPPs (like `AutowiredAnnotationBeanPostProcessor`) are registered earliest; if you need to run before/after them, choose your ordering interface deliberately.

  • Why should a @Bean method that returns a BeanPostProcessor be static?
    A non-static @Bean forces Spring to instantiate the enclosing @Configuration class (and transitively its other beans) early, before all BPPs are registered, so those beans may skip post-processing/auto-proxying. A static method avoids instantiating the config class.
  • What does the log message 'not eligible for getting processed by all BeanPostProcessors' mean?
    BeanPostProcessorChecker emitted it because that bean was instantiated during BPP registration, before every BPP existed. It may miss proxying — so no AOP, @Transactional, or @Async on it.
  • Can you make a BeanPostProcessor itself transactional or AOP-advised?
    No. BPPs are excluded from processing by other BPPs, so auto-proxying advice never applies to them.

saying these in an interview costs you the question

  • Injecting business services eagerly into a BPP without realizing it forces early instantiation
  • Expecting @Transactional/AOP advice to work on a BeanPostProcessor
  • Using a non-static @Bean method for a BPP and ignoring the startup warning
  • Confusing BeanFactoryPostProcessor (definitions) with BeanPostProcessor (instances)

context