When you inject a property string into a non-String type via @Value, how does Spring convert it, and what types work out of the box?
answer
- Property = String → target type via ConversionService
- Boot = ApplicationConversionService + PropertyEditors
- Duration '10s', DataSize '10MB', enums by name
- Comma-delimited → array/List/Set
- Custom: Converter<String,T> (+ @ConfigurationPropertiesBinding)
basics
~20 sProperty values are Strings. Spring converts the injected String to the target type using the ConversionService (plus PropertyEditors). Out of the box it handles primitives, enums, Duration, DataSize, arrays/List/Set/Map from delimited text, and more. Register a custom Converter for unsupported types.
solid answer
~40 sA `@Value("${...}")` placeholder always resolves to a String; the target member may be `int`, `boolean`, an enum, `Duration`, `List<String>`, etc. Spring bridges the gap with type conversion. The `TypeConverter` consults the `ConversionService` (Spring Boot installs `ApplicationConversionService`) and legacy `PropertyEditor`s. Built-in support covers primitives and wrappers, `BigDecimal`, enums (by name), `Charset`, `Locale`, `Duration`/`Period` (ISO-8601 or `10s`/`5m`), `DataSize` (`10MB`), `Resource`, and comma-delimited strings into arrays, `List`, `Set`. For anything custom you register a `Converter<String, T>` (or a `@ConfigurationPropertiesBinding` converter in Boot) with the `ConversionService`. If conversion fails you get a `ConversionFailedException`/`TypeMismatchException` at startup. SpEL `#{...}` values are already typed and generally bypass this string-conversion step.
code
java · 23 lines@Component
public class TimeoutConfig {
@Value("${task.timeout:30s}") // ISO-ish shorthand -> Duration
private Duration timeout;
@Value("${cache.max-size:10MB}") // -> DataSize
private DataSize maxSize;
@Value("${app.mode:PROD}") // -> enum by name
private Mode mode;
@Value("${app.hosts:a,b,c}") // comma-delimited -> List<String>
private List<String> hosts;
enum Mode { DEV, PROD }
}
// Custom type support
@Component
@ConfigurationPropertiesBinding
class MoneyConverter implements Converter<String, Money> {
public Money convert(String source) { return Money.parse(source); }
}go deeper
Know that Spring auto-converts "8080" to int and "true" to boolean.
Name the ConversionService and list common built-in target types (enum, Duration, List).
Explain the ConversionService + PropertyEditor pipeline, comma-delimited collections, and registering a custom Converter.
Weigh scattered @Value conversions vs @ConfigurationProperties with @ConfigurationPropertiesBinding converters and validation for maintainable typed config.
**Why conversion is needed.** Property sources store everything as text. When `@Value("${server.port}") int port` runs, Spring has the String `"8080"` but the field is `int`. The framework must convert. **The conversion machinery.** - `ConversionService` — the modern SPI. In Spring Boot the context uses `ApplicationConversionService`, which bundles core converters plus Boot-specific ones. It exposes `convert(source, TargetType)`. - `PropertyEditor` — the legacy JavaBeans mechanism; Spring still registers many `PropertyEditor`s (e.g., `CustomNumberEditor`, `ClassEditor`) via `PropertyEditorRegistry`. `SimpleTypeConverter`/`BeanWrapperImpl` try the `ConversionService` first, then editors. **Built-in conversions (common ones).** Primitives + wrappers (`int`, `long`, `boolean`, `double`…), `BigInteger`/`BigDecimal`, `String`, enums (matched by constant name), `Charset`, `Locale`, `TimeZone`, `UUID`, `java.time` types, `Duration` and `Period` (Boot supports `PT10S`, `10s`, `500ms`, `5m`), `DataSize` (`10MB`, `512KB`), `Resource`/`Path`/`File`, `Class`, and delimited collections: `"a,b,c"` → `String[]`, `List<String>`, `Set<String>`. `Map` can be bound too (better via `@ConfigurationProperties`). **Collections nuance.** For `@Value`, comma is the default delimiter for array/list conversion (via `StringToArrayConverter`/`StringToCollectionConverter`). Whitespace is trimmed per element. For richer structures prefer `@ConfigurationProperties`, or split explicitly with SpEL. **Custom types.** Implement `org.springframework.core.convert.converter.Converter<String, Money>` and register it (as a `@Bean` annotated `@ConfigurationPropertiesBinding` in Boot, or add to a `ConversionService`). Then `@Value("${price}") Money price` works. **Failure behavior.** Bad text (`@Value("${port}") int port` with `port=abc`) throws at context startup — a `TypeMismatchException`/`ConversionFailedException` wrapping `NumberFormatException`. Enum mismatch throws similarly. Fail-fast is intentional. **SpEL vs conversion.** A `#{...}` expression usually yields a already-typed value, so the String-conversion path is not exercised the same way; but if the SpEL result type differs from the target, the `ConversionService` may still adapt it. **When to use.** Rely on built-in conversions for standard settings. For domain types or validated groups of properties, move to `@ConfigurationProperties` + a registered converter rather than sprinkling custom parsing across `@Value`s.
- How do you inject a property into a custom domain type like Money?Register a Converter<String, Money> with the ConversionService (in Boot, a @Bean annotated @ConfigurationPropertiesBinding). Then @Value("${price}") Money price converts automatically.
- What happens if @Value("${port}") int port receives port=abc?Startup fails with a TypeMismatchException/ConversionFailedException wrapping NumberFormatException — conversion errors are fail-fast.
saying these in an interview costs you the question
- Claiming @Value can only inject Strings
- Thinking conversion happens at runtime lazily rather than at injection/startup
- Assuming enums match case-insensitively or by ordinal (they match by exact constant name)
- Believing you must manually parse Duration/DataSize