Interview Prep Zoneby CuriouserLabs

Question 2 of 5 · senior level

@ConfigurationProperties vs @Value — which do you standardize on and why? How do you make configuration type-safe?

🎤 Say this first

Standardize on @ConfigurationProperties: groups related settings into a typed, testable object; supports relaxed binding, nested structures, lists/maps, durations/data-sizes, JSR-303 validation at startup (fail fast on bad config), constructor binding for immutability (records!), and metadata for IDE completion. @Value remains for one-off simple injections and SpEL expressions. The deeper principle: configuration is an API — give it types, validation and documentation like one.

The full picture

Config as a validated, immutable type
@ConfigurationProperties(prefix = "acme.payment")
@Validated
public record PaymentProperties(
    @NotNull URI endpoint,
    @DurationUnit(ChronoUnit.SECONDS) @DefaultValue("5s") Duration timeout,
    @Min(1) @Max(64) @DefaultValue("8") int maxConcurrent,
    List<@NotBlank String> allowedCurrencies,
    Retry retry
) {
    public record Retry(@DefaultValue("3") int maxAttempts,
                        @DefaultValue("200ms") Duration backoff) {}
}
// + @EnableConfigurationProperties(PaymentProperties.class) or @ConfigurationPropertiesScan
// Bad config -> BindValidationException AT STARTUP with the exact property path.
// vs @Value("${acme.payment.timeout}") long timeoutMs; // stringly, scattered, unvalidated
  • Startup validation is the killer feature: a typo'd duration fails the deploy, not the 3am code path that first reads it.
  • Records/constructor binding give immutable config — no mutable singleton settings drifting at runtime.
  • spring-boot-configuration-processor generates metadata → IDE autocompletion and docs for your own properties; platform teams should treat that as mandatory.
  • Testing: bind in isolation with new Binder(source).bind("acme.payment", …) or context-runner tests — config bugs get unit tests like code bugs.
  • @Value niche kept honest: single values, SpEL (#{systemProperties…}), places where a whole properties class is ceremony.

🔄 Likely follow-up questions

  • How does constructor binding differ from setter binding for nested objects?
  • How do you handle deprecating/renaming a property without breaking deploys? (metadata deprecation entries)
  • Map<String, X> binding — when is it the right modeling tool?