When would you use @Value with SpEL, and what are its limits versus @ConfigurationProperties?
answer
- ${} placeholder + :default
- #{} SpEL compute / bean refs
- placeholder resolves before SpEL
- no relaxed binding, no groups, no @Validated
- not on constructor-bound params
basics
~10 sUse @Value for a single value, optionally computed with SpEL (#{...}) or defaulted (${prop:default}). Its limits: no relaxed binding, no nested/collection binding, no built-in validation, and it resolves eagerly per injection point.
solid answer
~40 s@Value injects one value into a field, parameter or method. It supports two syntaxes: property placeholders ${my.prop} (with an optional default ${my.prop:fallback}) and SpEL expressions #{...} for computation, e.g. #{systemProperties['user.region'] ?: 'US'} or #{T(java.time.Duration).ofSeconds(30)}. You can nest a placeholder inside SpEL: #{'${app.name}'.toUpperCase()}. Reach for @Value when you need a single, possibly-computed value and a full config class is overkill. Its limits versus @ConfigurationProperties: no relaxed binding (the placeholder must match exactly), no binding of nested objects, lists or maps as a group, no JSR-303 @Validated support, no participation in configuration metadata, and each injection point is independent so the same setting can drift. For any cohesive group of settings, prefer @ConfigurationProperties. Note @Value can't be used on constructor-bound @ConfigurationProperties parameters.
code
java · 14 lines@Component
class ReportService {
@Value("${report.dir:/var/reports}") // placeholder + default
private String dir;
@Value("#{T(java.time.Duration).ofSeconds(45)}") // SpEL literal
private Duration timeout;
@Value("#{'${report.recipients}'.split(',')}") // placeholder inside SpEL
private String[] recipients;
@Value("#{systemEnvironment['HOSTNAME'] ?: 'local'}") // env + Elvis default
private String host;
}go deeper
Know ${...} injects a value and :default provides a fallback.
Distinguish ${} placeholder from #{} SpEL and list the main limits vs @ConfigurationProperties.
Explain resolution order, appropriate use cases, and why groups belong in @ConfigurationProperties.
Set guidance limiting SpEL in @Value to keep config testable; prefer typed config for anything cohesive or validated.
## The two @Value syntaxes `@Value` (`org.springframework.beans.factory.annotation.Value`) accepts a String that Spring resolves at injection time: 1. **Property placeholder** — `${...}` resolved from the `Environment`: - `@Value("${server.port}")` - With a default: `@Value("${app.timeout:30}")` (used if the property is absent). 2. **SpEL (Spring Expression Language)** — `#{...}` evaluated as an expression: - `@Value("#{systemProperties['user.timezone']}")` - `@Value("#{T(java.lang.Math).random() * 100}")` - Bean references: `@Value("#{someBean.someProperty}")` ## Combining them A placeholder can be embedded *inside* a SpEL literal so you compute over a property value: ```java @Value("#{'${app.name}'.toUpperCase()}") private String upperName; @Value("#{'${app.tags}'.split(',')}") private String[] tags; // manual split; not real relaxed list binding ``` Order matters: placeholders (`${}`) are resolved *before* SpEL (`#{}`) evaluates. ## Where @Value can be applied Fields, setter/constructor parameters (of ordinary beans), and `@Bean` method parameters. It is resolved **eagerly** at injection. ## Limits vs @ConfigurationProperties | Concern | @Value | @ConfigurationProperties | |---|---|---| | Relaxed binding | No — exact name | Yes | | Nested objects / list / map as a group | No | Yes | | JSR-303 validation (@Validated) | No (native) | Yes | | Config metadata / IDE hints | No | Yes | | Grouping / cohesion | Scattered per injection point | One typed bean | | SpEL computation | Yes | No | ## Gotchas - **No relaxed binding**: `@Value("${app.firstName}")` will not match a property written `app.first-name`. You must reference the exact key. - Referencing a missing property with no default throws at startup (`IllegalArgumentException` / placeholder resolution failure). - SpEL over-use makes config hard to test and reason about; keep expressions trivial. - `@Value` is **not** supported on **constructor-bound** `@ConfigurationProperties` parameters. - Splitting a comma list via SpEL is manual and lacks the type conversion/relaxed handling of `@ConfigurationProperties` list binding. ## When to use which - **@Value**: one standalone value, a small computed/default, or a bean-reference expression. - **@ConfigurationProperties**: any cohesive set of settings, anything needing validation, nesting, relaxed binding, or metadata.
- How do you give @Value a default when the property is missing?Use the colon syntax inside the placeholder: @Value("${app.timeout:30}") uses 30 if app.timeout is not defined.
- What resolves first in @Value("#{'${app.name}'.trim()}") — the placeholder or the SpEL?The ${...} placeholder is resolved first, then the resulting string is evaluated by the #{...} SpEL expression.
- Why won't @Value("${app.first-name}") pick up a value set as app.firstName?@Value has no relaxed binding; the placeholder key must match exactly. Only @ConfigurationProperties treats those spellings as equivalent.
saying these in an interview costs you the question
- Believing @Value supports relaxed binding across kebab/camel forms
- Thinking @Value can bind a nested config object
- Assuming SpEL and placeholder are the same syntax
- Using @Value on constructor-bound @ConfigurationProperties parameters