skip to content

How can you inspect or mutate BeanDefinitions before beans are instantiated, and what are the ordering/safety rules?

level: principalimportance: should knowfreq 25%

answer

  1. BFPP edits definitions before instantiation
  2. BDRPP can add/remove definitions (earlier callback)
  3. order: registry PPs first, then factory PPs; PriorityOrdered→Ordered→rest
  4. never getBean app beans in a BFPP
  5. BFPP = metadata phase, BPP = instance phase

basics

~20 s

Use a BeanFactoryPostProcessor (or BeanDefinitionRegistryPostProcessor) which Spring invokes after all definitions are loaded but before any bean is created. Through the registry/factory you read and edit definitions — change scope, property values, lazy flag, etc. — and the container instantiates from your changes.

solid answer

~30 s

Spring's container refresh has a phase where **all BeanDefinitions are registered but no application beans are instantiated yet**. `BeanFactoryPostProcessor.postProcessBeanFactory(ConfigurableListableBeanFactory)` runs here: you enumerate `getBeanDefinitionNames()`, fetch each `BeanDefinition`, and mutate scalar flags or `getPropertyValues()`/`getConstructorArgumentValues()`. `BeanDefinitionRegistryPostProcessor` extends it with an earlier `postProcessBeanDefinitionRegistry` callback that can also **add/remove** definitions. Ordering: registry-post-processors run before plain factory-post-processors; within each, `PriorityOrdered` then `Ordered` then unordered. Safety rules: don't call `getBean` for application beans here (it forces premature instantiation, freezing their definitions and bypassing later processors); BFPPs themselves are instantiated early, so keep their dependencies minimal. This is exactly how `PropertySourcesPlaceholderConfigurer` resolves `${...}` placeholders in definitions.

code

java · 16 lines
java
public class ForceLazyForProfile implements BeanFactoryPostProcessor, Ordered {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
        for (String name : bf.getBeanDefinitionNames()) {
            BeanDefinition def = bf.getBeanDefinition(name);
            if ("com.acme.ReportGenerator".equals(def.getBeanClassName())) {
                def.setLazyInit(true);                 // mutate metadata
                def.setScope(BeanDefinition.SCOPE_PROTOTYPE);
            }
        }
        // Do NOT call bf.getBean(...) for application beans here.
    }

    @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; }
}

go deeper

for a junior

Know a BeanFactoryPostProcessor can change bean config before beans are created.

for a middle

Distinguish BFPP (definitions) from BeanPostProcessor (instances) and name PropertySourcesPlaceholderConfigurer as an example.

for a senior

Explain the refresh window, BDRPP for add/remove, and the don't-getBean-here rule.

for a principal

Detail the full ordering (registry PPs vs factory PPs, PriorityOrdered/Ordered), static @Bean recommendation, BeanPostProcessorChecker warnings, and merged-vs-raw mutation.

## The window `AbstractApplicationContext.refresh()` runs `invokeBeanFactoryPostProcessors(beanFactory)` **after** `loadBeanDefinitions` populated the registry but **before** `finishBeanFactoryInitialization` pre-instantiates singletons. In this window the definitions exist as pure metadata and are freely mutable. ## The two interfaces ### `BeanFactoryPostProcessor` (BFPP) `void postProcessBeanFactory(ConfigurableListableBeanFactory bf)`. You get the factory (which is also a `BeanDefinitionRegistry`) and can: - `bf.getBeanDefinitionNames()` → iterate. - `bf.getBeanDefinition(name)` → read/mutate: `setScope`, `setLazyInit`, `setPrimary`, `getPropertyValues().add(...)`, `getConstructorArgumentValues().addIndexedArgumentValue(...)`. - You can read but should not instantiate application beans. ### `BeanDefinitionRegistryPostProcessor` (BDRPP) Extends BFPP, adds the **earlier** `postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)` — the sanctioned place to **register or remove** definitions (add new beans, e.g. Spring Data repository proxies). Its BFPP method still runs later for mutation. ## Ordering (important for principals) During `invokeBeanFactoryPostProcessors`, Spring processes in strict order: 1. **`BeanDefinitionRegistryPostProcessor`s** first (their registry callback), subdivided by `PriorityOrdered` → `Ordered` → the rest. Newly-registered BDRPPs are discovered iteratively. 2. Then the **BFPP** `postProcessBeanFactory` callbacks, again `PriorityOrdered` → `Ordered` → unordered. `ConfigurationClassPostProcessor` is itself a `BDRPP` (registered as an infrastructure bean) that parses `@Configuration`/`@Bean`/`@Import` into definitions — so your custom processors interact with it via ordering. ## Built-in example `PropertySourcesPlaceholderConfigurer` is a `BFPP` that walks definitions and resolves `${...}` placeholders in their property values against the `Environment`. This is why placeholder resolution happens on **metadata**, before beans exist. ## Safety rules & gotchas - **Never eagerly `getBean` application beans inside a BFPP**: it forces early instantiation, so those beans miss later `BeanPostProcessor`s (e.g. AOP), and their definitions are effectively frozen. Spring logs warnings (`BeanPostProcessorChecker`) about beans not eligible for all post-processing. - **Keep BFPP dependencies minimal**: BFPPs are instantiated very early; injecting heavy application beans drags them into premature creation. Prefer `Environment`/`BeanFactory` injection or make them `static @Bean` methods (which is why `PropertySourcesPlaceholderConfigurer`/`static` BFPP `@Bean` methods are recommended). - **Don't confuse with `BeanPostProcessor`**: BFPP operates on **definitions** (before instantiation); BPP operates on **instances** (during initialization). Different phases entirely. - After the factory is **frozen** (`freezeConfiguration`) and singletons pre-instantiated, definition mutation of already-created singletons has no effect. - Mutating a merged definition vs a raw definition: mutate the **raw** registered definition here; merged copies are derived later. ## When to use - Environment-driven rewrites of definitions (scope by profile, toggling lazy). - Bulk registration of beans from external descriptors (via BDRPP). - Framework/library integration. For ordinary apps, prefer `@Conditional`, profiles, and annotations before reaching for a BFPP.

  • What's the difference between BeanFactoryPostProcessor and BeanPostProcessor?
    BeanFactoryPostProcessor operates on BeanDefinitions (metadata) after loading but before any bean is instantiated. BeanPostProcessor operates on bean instances during their initialization (before/after init callbacks). Different phases: definitions vs instances.
  • Why is registering a placeholder configurer or BFPP via a static @Bean method recommended?
    Because BFPPs are instantiated very early; a non-static @Bean method would require instantiating the enclosing @Configuration class (and its dependencies) prematurely. A static method avoids that, keeping the BFPP free of early-instantiation side effects.
  • How does Spring order multiple post-processors?
    BeanDefinitionRegistryPostProcessors run first (registry callback), then plain BeanFactoryPostProcessors; within each group it honors PriorityOrdered, then Ordered, then unordered. Newly registered registry post-processors are discovered and processed iteratively.

saying these in an interview costs you the question

  • Confusing BeanFactoryPostProcessor with BeanPostProcessor (definition phase vs instance phase).
  • Calling getBean for application beans inside a BFPP, forcing premature instantiation and skipped post-processing.
  • Assuming you can add/remove definitions from postProcessBeanFactory (adding is the BDRPP registry callback's job).
  • Injecting heavy application beans into a BFPP without realizing it drags them into early creation.

context