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?
answer
- No @Value on static fields (setter hack)
- Field @Value = null inside constructor
- Use ctor-param @Value for construction-time + final
- BFPP created early → placeholders unresolved; use Environment
- 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 sThree 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// (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
Just know @Value doesn't work on static fields.
Explain injection timing: field @Value is null in the constructor; use constructor injection instead.
Add the one-time-binding nature (RefreshScope to refresh) and the setter trick for statics; prefer constructor injection for immutability/testability.
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