skip to content

Why must BeanFactoryPostProcessor (and BeanPostProcessor) @Bean methods be declared static inside a @Configuration class?

level: seniorimportance: should knowfreq 40%

answer

  1. post-processors created before config class enhanced
  2. non-static => config instantiated too early
  3. 'created too early' / 'cannot enhance' warning
  4. static = call method without instance
  5. static can't use @Value fields of the config

basics

~20 s

Because these post-processors are created very early, before the @Configuration class is processed. A static @Bean method can be called without instantiating the config class, so the container avoids creating it prematurely (which would trigger warnings and skip enhancement).

solid answer

~50 s

BeanFactoryPostProcessors and BeanPostProcessors run at the very start of the container lifecycle — a BFPP's postProcessBeanFactory fires before most other beans are instantiated. If you define one as a non-static @Bean method on a @Configuration class, Spring must instantiate that configuration class to invoke the method. But the config class is being instantiated *too early*, before it can be fully processed/CGLIB-enhanced. Spring logs a warning like 'Cannot enhance @Configuration bean definition ... its singleton instance is created too early', and any @Autowired/@Value inside that config class may not be honored — you lose the @Configuration proxying that makes inter-@Bean calls return singletons. Declaring the method static breaks the dependency: Spring can call the static factory method without creating the enclosing config instance, so the post-processor is produced cleanly and the config class is enhanced normally.

code

java · 22 lines
java
@Configuration
public class InfraConfig {

    // WRONG: non-static forces InfraConfig to be instantiated too early,
    // before it can be CGLIB-enhanced -> 'created too early' warning.
    // @Bean
    // public BeanFactoryPostProcessor bfpp() { return new MyBfpp(); }

    // RIGHT: static -> invoked without an InfraConfig instance.
    @Bean
    public static BeanFactoryPostProcessor bfpp() {
        return new MyBfpp();
    }

    // If the post-processor needs config, inject via method params,
    // NOT via @Autowired fields on this class (static has no instance).
    @Bean
    public static BeanFactoryPostProcessor configurableBfpp(
            @Value("${infra.flag:false}") boolean flag) {
        return new MyBfpp(flag);
    }
}

go deeper

for a junior

Just remember: post-processor @Bean methods should be static.

for a middle

Explain the early-instantiation timing that motivates static.

for a senior

Articulate the CGLIB enhancement loss, the 'created too early' warning, and the static trade-off (no instance fields).

for a principal

Relate to proxyBeanMethods modes, ConfigurationClassPostProcessor ordering, and how framework/auto-config code exposes post-processors statically.

## The rule When you register a `BeanFactoryPostProcessor` (BFPP) or `BeanPostProcessor` (BPP) via a `@Bean` method inside a `@Configuration` class, that method **should be `static`**: ```java @Configuration public class InfraConfig { @Bean public static BeanFactoryPostProcessor myBfpp() { ... } // static } ``` ## Why — the timing conflict Spring's `refresh()` processes post-processors **before** ordinary singletons: 1. `invokeBeanFactoryPostProcessors()` — BFPPs must already exist to run here. 2. `registerBeanPostProcessors()` — BPPs must be instantiated here. 3. only later are regular singletons created. To *produce* a BFPP/BPP that lives on a config class, Spring must call your `@Bean` method. If the method is an **instance** method, calling it requires an **instance of the @Configuration class**. So Spring is forced to instantiate `InfraConfig` at step 1/2 — far earlier than normal beans. The problem: `@Configuration` classes are normally **CGLIB-enhanced** — Spring subclasses them so that inter-`@Bean` method calls are intercepted and return the shared singleton ("full" configuration mode). That enhancement is applied by `ConfigurationClassPostProcessor` (itself a `BeanDefinitionRegistryPostProcessor`) and by proxy-creating BPPs. If the config class is instantiated **before** those processors have done their work, it is created as a **raw, un-enhanced** instance. Spring detects this and logs: ``` Cannot enhance @Configuration bean definition 'infraConfig' since its singleton instance has been created too early. ``` Consequences of an un-enhanced config instance created too early: - Inter-`@Bean` method calls inside it may create **new** objects instead of returning singletons. - `@Autowired`/`@Value` fields on that config class may be **unresolved**, because the BPPs that inject them aren't registered yet. ## Why static fixes it A `static` `@Bean` method is invoked on the class, **not** on an instance. Spring can call `InfraConfig.myBfpp()` without ever constructing `InfraConfig`. So: - The post-processor is produced at the right early moment. - The `@Configuration` class instance is NOT created prematurely, so it is enhanced normally later. Trade-off: a `static` method **cannot use** the config class's `@Autowired`/`@Value` instance fields (there is no instance). If your post-processor needs configuration, pass it via method parameters (Spring injects `@Bean` method parameters) or read the `Environment`/`BeanFactory` inside the post-processor itself. ## Does this apply everywhere? - **@Configuration (proxyBeanMethods=true, the default):** yes, the static rule matters most here because enhancement is at stake. - **@Configuration(proxyBeanMethods=false) / lite mode / plain @Component:** less severe, but the early-instantiation timing issue and the Spring warning can still appear, so static is still the safe, recommended practice. - Framework code follows this too — e.g. the auto-configured `PropertySourcesPlaceholderConfigurer` is exposed via a static method. ## How you notice it in the wild The tell-tale sign is a startup WARN log about a config bean's instance being created too early / not being eligible for enhancement, often accompanied by mysteriously un-injected fields in that config class or duplicate beans. The fix is almost always: add `static` to the post-processor's `@Bean` method.

  • What warning does Spring log when you get this wrong, and what breaks?
    Something like 'Cannot enhance @Configuration bean definition ... its singleton instance is created too early'. The config class is created un-enhanced, so inter-@Bean calls may not return singletons and @Autowired/@Value on that class may be unresolved.
  • A downside of static @Bean methods is that they can't access instance @Autowired/@Value fields. How do you pass configuration to the post-processor then?
    Declare parameters on the static @Bean method — Spring resolves and injects them (including @Value on the parameter) — or read the Environment/BeanFactory from inside the post-processor's own methods.

saying these in an interview costs you the question

  • Saying static is only a style preference with no functional effect.
  • Claiming a non-static post-processor bean simply won't be created.
  • Not knowing it stems from post-processors running before config enhancement.
  • Thinking @Lazy or @DependsOn is the correct fix.

context