Skip to content

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.

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories