Build1 publisher3 min readPublished
In Spring, the hook you need is decided by the clock, not by the name
A dev.to walkthrough pins the BeanFactoryPostProcessor and BeanPostProcessor split to container ordering: blueprints in the first pass, live objects in the second.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction
What happened
- The dev.to article describes BeanFactoryPostProcessor and BeanPostProcessor as Spring's two official extension points and calls their names the single most confused pair in the framework.
- When the Spring context starts it does not build beans in one motion; it works in two distinct passes, and each of the two post-processors hooks into exactly one of them, which is the whole distinction.
- In the first pass the container reads every source of configuration (@Configuration classes, component scans, any XML) and turns each into a bean definition: a plain metadata object describing the class, scope, constructor arguments and property values. No object exists yet.
- In the second pass, once all definitions are collected, the container instantiates: it walks the definitions, builds live objects, wires each one's dependencies and runs its init code.
- BeanFactoryPostProcessor runs at the seam between the two passes: after every bean definition is loaded, but before a single bean is instantiated.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
A dev.to walkthrough of Spring's container startup argues that BeanFactoryPostProcessor and BeanPostProcessor are the single most confused pair of names in the framework [1]. The useful distinction is not semantic but temporal: the container starts in two separate passes, and each hook attaches to exactly one of them [2].
The first pass reads every source of configuration, the `@Configuration` classes, component scans and any XML, and turns each into a bean definition: a metadata object describing class, scope, constructor arguments and property values, with no object built yet [3]. The second pass walks those definitions, instantiates live objects, wires each one's dependencies and runs its init code [4].
BeanFactoryPostProcessor sits at the seam. It runs after every bean definition is loaded and before a single bean is instantiated [5]. It has one method, `postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)` [6], and the argument is the container holding all the definitions, so you can walk it, read any definition and change it before that bean is ever built [7]. The example given is a component that loops `getBeanDefinitionNames()` and calls `setLazyInit(true)` on each definition [8]; because it runs before instantiation, the edit lands in time for the container to read the modified blueprint when it builds [9].
Most teams already depend on this without noticing. `PropertySourcesPlaceholderConfigurer`, which turns `${...}` placeholders into real values, is a BeanFactoryPostProcessor that Spring registers for you, and it runs in the between-passes window so placeholder resolution is wired up before any bean that needs a value is built [10]. A close relative, BeanDefinitionRegistryPostProcessor, runs a moment earlier and can add whole new definitions rather than tweak existing ones; per the source, that is how `@Configuration` classes become bean definitions in the first place [11]. The rule the piece offers is blunt: a BeanFactoryPostProcessor sees definitions, never instances, and wanting the actual built object means you have reached for the wrong hook [12].
BeanPostProcessor is the other one. It runs inside the second pass, after the container has built a bean and injected its dependencies, around the moment the bean's init code runs [13]. Its two methods, `postProcessBeforeInitialization(Object bean, String beanName)` and `postProcessAfterInitialization(Object bean, String beanName)` [14], bracket that init step, the callback that fires once a bean is fully wired, such as a method marked `@PostConstruct` [15].
That ordering is the whole failure mode. An attempt to change how a bean is constructed from inside a BeanPostProcessor cannot take effect, because the object handed to it has already been instantiated and wired [1]. The reverse mistake is quieter: a BeanFactoryPostProcessor cannot inspect or wrap a live instance, because in its window none exists [2]. Wrapping a service in a transaction proxy is listed among the reasons Spring needs to reach into its own build process at all [16], and both BeanPostProcessor methods are typed to return `Object` rather than `void` [14], which is the mechanical opening for handing back a substitute; the excerpt does not spell that contract out [3].
Worth checking in your own code: whether each custom hook reaches for `getBeanDefinition` or for the built bean, since that single call decides which pass you belong in. The excerpt breaks off mid-sentence while describing `postProcessBeforeInitialization` [17], so what the container does with a returned replacement object is not documented here, and that is the detail to confirm against the reference documentation before relying on it.