How does Spring Boot bind strings like "30s" or "10MB" to Duration and DataSize, and how do you control default units?
answer
- Duration: s/m/h/d or PT.. ISO-8601
- DataSize: B/KB/MB/GB, base-1024
- @DurationUnit / @DataSizeUnit for bare numbers
- suffix always overrides default unit
- convert first, then validate
basics
~10 sThe binder's converters turn suffixed strings into java.time.Duration and org.springframework.util.unit.DataSize. Suffixes like s/m/h and B/KB/MB/GB are parsed automatically; use @DurationUnit and @DataSizeUnit to set the unit for bare numbers.
solid answer
~30 sSpring Boot registers converters in its ApplicationConversionService that the @ConfigurationProperties binder uses. A String like "30s", "5m", "2h" (or ISO-8601 "PT30S") binds to java.time.Duration; "10MB", "512KB", "1GB" binds to org.springframework.util.unit.DataSize. When the value is a bare number with no suffix, the default unit applies: annotate the field with @DurationUnit(ChronoUnit.SECONDS) or @DataSizeUnit(DataUnit.MEGABYTES) (both in org.springframework.boot.convert) to say what a unit-less number means. An explicit suffix always overrides the default. A key gotcha: DataSize is binary — DataUnit.KILOBYTES is 1024 bytes, so 1MB = 1024*1024 bytes. Conversion happens before validation, so JSR-380 constraints (and Hibernate's @DurationMin/@DurationMax) run against the already-converted value.
code
java · 30 linesimport java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties(prefix = "app.cache")
@Validated
public class CacheProperties {
// "30s" -> 30 seconds; bare "30" -> 30 seconds (default unit)
@DurationUnit(ChronoUnit.SECONDS)
private Duration ttl = Duration.ofSeconds(30);
// "10MB" -> 10 * 1024 * 1024 bytes; bare "10" -> 10 MB (default unit)
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize maxSize = DataSize.ofMegabytes(10);
// getters/setters omitted
}
/*
app:
cache:
ttl: 5m # explicit suffix overrides the SECONDS default
max-size: 512KB # 512 * 1024 bytes
*/go deeper
Know Boot converts strings like 30s and 10MB to Duration/DataSize automatically.
Explain @DurationUnit/@DataSizeUnit for default units, suffix precedence, and the base-1024 DataSize gotcha.
Locate the converters in ApplicationConversionService (org.springframework.boot.convert) and note convert-then-validate ordering plus Hibernate @DurationMin/@DurationMax.
Prefer semantic value types over raw longs across a config surface and standardize unit conventions to avoid ambiguity.
## The types - **`java.time.Duration`** — a time span. Boot binds human-friendly strings to it. - **`org.springframework.util.unit.DataSize`** — Boot's own value type for byte sizes (with `DataUnit`: B, KB, MB, GB, TB). ## How conversion is wired The `@ConfigurationProperties` **binder** uses Boot's `ApplicationConversionService`, which registers dedicated converters — e.g. a String→Duration converter and a String→DataSize converter (`org.springframework.boot.convert` package). So no manual parsing is needed; you just declare the target type on the property. ## Duration formats accepted - **Suffixed simple format**: `ns` (nanos), `us` (micros), `ms` (millis), `s` (seconds), `m` (minutes), `h` (hours), `d` (days) → `"30s"`, `"5m"`, `"2h"`, `"1d"`. - **ISO-8601**: `"PT30S"`, `"PT5M"`, `"P1D"`. - **Bare number**: `"30"` — interpreted using the **default unit**. ### Setting the Duration default unit `@DurationUnit(ChronoUnit.SECONDS)` (from `org.springframework.boot.convert`) says a unit-less number means seconds. If the string carries a suffix, the suffix wins and the annotation is ignored for that value. ## DataSize formats accepted - **Suffixed**: `"10MB"`, `"512KB"`, `"1GB"`, `"2TB"`, `"1024B"`. - **Bare number**: `"1048576"` — interpreted using the **default unit** (bytes by default). ### Setting the DataSize default unit `@DataSizeUnit(DataUnit.MEGABYTES)` says a unit-less number means megabytes. ### Binary gotcha `DataUnit` is **base-1024**: `KILOBYTES = 1024`, `MEGABYTES = 1024^2`, etc. So `"1MB"` = 1,048,576 bytes, not 1,000,000. Don't assume decimal (SI) units. ## Also: Period Analogously, `java.time.Period` is supported with `@PeriodUnit(ChronoUnit.DAYS)` and suffixes `y/m/w/d`. ## Order: convert then validate Binding first **converts** the string to `Duration`/`DataSize`, then any JSR-380 constraints run on the resulting object. Note that `@Min`/`@Max` apply to numeric types, not `Duration`; to bound a `Duration` you use **Hibernate Validator's** `@DurationMin`/`@DurationMax` (Hibernate-specific, not core JSR-380). A malformed string (e.g. `"10 megabytes"`) fails at the *conversion* stage as a bind error, distinct from a constraint violation, but both abort startup. ## When to use Use `Duration`/`DataSize` types (instead of `long millis` / `long bytes`) so config reads naturally (`timeout: 30s`, `max-file-size: 10MB`) and is self-documenting, while the binder handles parsing and the default-unit annotations remove ambiguity.
- Is DataSize decimal or binary — how many bytes is 1MB?Binary. DataUnit is base-1024, so 1MB = 1024*1024 = 1,048,576 bytes, not 1,000,000.
- What does @DurationUnit(SECONDS) do when the value is "5m"?Nothing — the explicit m suffix wins, giving 5 minutes. The default unit only applies to unit-less numeric strings.
saying these in an interview costs you the question
- Assuming DataSize uses decimal 1000-based units instead of 1024
- Thinking @Min/@Max can bound a Duration (need Hibernate @DurationMin/@DurationMax)
- Believing you must manually parse timeout/size strings