skip to content

What are the timing and placement gotchas of @Value: injecting into static fields, reading the value in a constructor, and using @Value inside a BeanFactoryPostProcessor?

level: principalimportance: nice to knowfreq 35%

answer

  1. No @Value on static fields (setter hack)
  2. Field @Value = null inside constructor
  3. Use ctor-param @Value for construction-time + final
  4. BFPP created early → placeholders unresolved; use Environment
  5. Static @Bean for BFPP; @Value binds once (RefreshScope to refresh)

basics

~20 s

@Value doesn't work on static fields. With field injection the value isn't set until after construction, so reading it in the constructor gives null — use constructor-param @Value instead. And placeholders may not resolve inside a BeanFactoryPostProcessor because it is created before the placeholder configurer runs.

solid answer

~40 s

Three real gotchas. (1) `@Value` on a `static` field is ignored — Spring injects into instances, not statics; a common hack is a non-static setter that assigns the static field. (2) With *field* injection, the value is set by reflection *after* the constructor runs, so referencing that field in the constructor sees `null`/default; use a constructor parameter `@Value` so it's available during construction and the field can be `final`. (3) `@Value("${...}")` inside a `BeanFactoryPostProcessor` (or a bean it depends on) often fails to resolve, because `BeanFactoryPostProcessor`s — including `PropertySourcesPlaceholderConfigurer` itself — are instantiated very early, before placeholder resolution is wired for them; inject the `Environment` and call `getProperty` instead. Also `@Value` resolution is per-injection-point and not refreshed unless the bean is `@RefreshScope`.

code

java · 21 lines
java
// (2) constructor-param @Value: available in ctor, field can be final
@Service
public class RetryService {
    private final int maxRetries;
    public RetryService(@Value("${retry.max:3}") int maxRetries) {
        this.maxRetries = maxRetries;      // usable here; field injection would be null
    }
}

// (3) BFPP: don't rely on @Value placeholders; read Environment instead
@Configuration
public class InfraConfig {
    @Bean
    static PropertySourcesPlaceholderConfigurer pspc() { // static -> avoids early config init
        return new PropertySourcesPlaceholderConfigurer();
    }
    @Bean
    static MyBeanFactoryPostProcessor bfpp(ConfigurableEnvironment env) {
        return new MyBeanFactoryPostProcessor(env.getProperty("app.flag", "off"));
    }
}

go deeper

for a junior

Just know @Value doesn't work on static fields.

for a middle

Explain injection timing: field @Value is null in the constructor; use constructor injection instead.

for a senior

Add the one-time-binding nature (RefreshScope to refresh) and the setter trick for statics; prefer constructor injection for immutability/testability.

for a principal

Explain BFPP early-instantiation causing unresolved placeholders, the static @Bean fix, injecting Environment in early-lifecycle components, and an overall config strategy.

**1. Static fields.** Spring's dependency injection operates on bean *instances*. `@Value` (like `@Autowired`) on a `static` field is silently ignored — the field keeps its default. If you truly need a static holder, use the setter trick: ```java private static String url; @Value("${app.url}") public void setUrl(String v){ AppConst.url = v; } ``` This runs on a managed instance and assigns the static. It is a smell; prefer an injected singleton bean. **2. Constructor vs field timing.** Lifecycle order for a bean: constructor → field/setter injection (`@Autowired`/`@Value` populated by `AutowiredAnnotationBeanPostProcessor`) → `@PostConstruct`. So a field annotated `@Value` is still `null`/`0` inside the constructor. If you need the value during construction, annotate the *constructor parameter*: ```java public Svc(@Value("${retries:3}") int retries){ this.retries = retries; } ``` This also enables `final` fields, immutability, and framework-free unit tests. **3. BeanFactoryPostProcessor (BFPP) early-instantiation.** `PropertySourcesPlaceholderConfigurer` is itself a BFPP. BFPPs are created and run *before* ordinary bean instantiation, and importantly before `@Value` placeholder resolution is available to *them*. So a `@Value("${x}")` inside your own BFPP, or inside a `@Configuration` bean that another BFPP forces into early creation, may inject the raw literal `${x}` or fail. Two consequences: (a) don't rely on `@Value` placeholders in BFPPs/`BeanPostProcessor`s that are eagerly created — inject `Environment` and read `environment.getProperty("x")`; (b) a `@Bean` method returning a BFPP should be `static` so its enclosing `@Configuration` isn't instantiated too early (avoiding the 'not eligible for auto-proxying / cannot resolve placeholder' warnings). **4. Resolution is a one-time injection.** `@Value` binds once at injection time; changing the underlying property later does not update already-injected values unless the bean is recreated (e.g., Spring Cloud `@RefreshScope`). For dynamically re-readable config, inject `Environment` or use `@ConfigurationProperties` with rebinding. **5. Optional/absent values.** For 'may be absent' semantics, `@Value("${x:}")` gives empty string; combining with `Optional` isn't automatic for `@Value` — you handle emptiness yourself or use `@ConfigurationProperties`. **When it matters.** These bite in infrastructure code (custom post-processors, static utility holders, config classes producing early beans). The senior guidance: keep configuration in ordinary singleton beans via constructor injection, use `Environment.getProperty` in early-lifecycle components, and reserve statics for genuine constants.

  • Why should a @Bean method that returns a BeanFactoryPostProcessor be declared static?
    BFPPs are instantiated very early; a non-static @Bean forces its @Configuration class to be created too early, before placeholder resolution and proxying are ready, causing unresolved-placeholder warnings. Making it static breaks that premature instantiation.
  • If a property value changes at runtime, will an already-injected @Value field update?
    No. @Value binds once at injection time. You need bean recreation (e.g., Spring Cloud @RefreshScope) or read from Environment/@ConfigurationProperties dynamically.
  • How can you make a value available as a static field despite @Value ignoring static fields?
    Use a non-static setter annotated with @Value that assigns the static field on a managed instance, or better, inject a singleton bean instead of using statics.

saying these in an interview costs you the question

  • Believing @Value populates static fields
  • Reading a field-injected @Value inside the constructor and expecting the value
  • Assuming @Value placeholders always resolve inside BeanFactoryPostProcessors
  • Expecting @Value to auto-refresh when the property changes
  • Making a BFPP @Bean non-static and ignoring the resulting placeholder warnings

context