java yaml 84 lines · 3 tabs

Bind and Validate Grouped App Settings with @ConfigurationProperties in Spring Boot

Shared by codesnips Aug 2026
3 tabs
package com.example.storage;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.validation.annotation.Validated;

@Validated
@ConfigurationProperties(prefix = "app.storage")
public class StorageProperties {

    @NotBlank
    private String endpoint;

    @NotBlank
    @Pattern(regexp = "[a-z0-9.\\-]{3,63}", message = "bucket must be a valid DNS-style name")
    private String bucket;

    @DurationUnit(ChronoUnit.SECONDS)
    private Duration timeout = Duration.ofSeconds(10);

    @Valid
    private final Retry retry = new Retry();

    public String getEndpoint() { return endpoint; }
    public void setEndpoint(String endpoint) { this.endpoint = endpoint; }

    public String getBucket() { return bucket; }
    public void setBucket(String bucket) { this.bucket = bucket; }

    public Duration getTimeout() { return timeout; }
    public void setTimeout(Duration timeout) { this.timeout = timeout; }

    public Retry getRetry() { return retry; }

    public static class Retry {

        @Min(1)
        private int maxAttempts = 3;

        @DurationUnit(ChronoUnit.MILLIS)
        private Duration initialBackoff = Duration.ofMillis(100);

        public int getMaxAttempts() { return maxAttempts; }
        public void setMaxAttempts(int maxAttempts) { this.maxAttempts = maxAttempts; }

        public Duration getInitialBackoff() { return initialBackoff; }
        public void setInitialBackoff(Duration initialBackoff) { this.initialBackoff = initialBackoff; }
    }
}
3 files · java, yaml Explain with highlit

This snippet shows how Spring Boot binds a tree of related settings into a single type-safe object and fails fast at startup when the configuration is wrong. The pattern replaces scattered @Value injections with one cohesive, validated class, which is the recommended approach once a group of properties starts to grow related sub-settings.

In StorageProperties, the class is annotated with @ConfigurationProperties(prefix = "app.storage") and @Validated. The @Validated annotation is what activates JSR-380 constraint checking on the bound object; without it the annotations are inert. Constraints like @NotBlank, @Min, and @Pattern describe the contract the configuration must satisfy, and @DurationUnit lets a plain number in YAML be interpreted as seconds and bound to a java.time.Duration. Nested settings live in the inner Retry type, exposed through a getter so binding recurses into app.storage.retry.*; @Valid on that field cascades validation into the nested object, a step developers frequently forget.

The @ConstructorBinding-free, mutable-getter style is used here for compatibility with older Spring Boot versions, though modern versions also support immutable constructor binding.

In StorageConfig, @EnableConfigurationProperties(StorageProperties.class) registers the properties class as a bean so it can be injected anywhere, and a StorageClient bean is wired from the already-validated settings. Because validation runs during bean creation, an invalid bucket or a negative maxAttempts aborts the context refresh with a clear BindValidationException rather than surfacing as a runtime NullPointerException deep in a request.

In application.yml, the app.storage block mirrors the property structure exactly, including the human-friendly timeout: 5s and initial-backoff: 200ms values that Spring parses into Duration. Note the relaxed binding: kebab-case keys map to camelCase fields automatically.

The trade-off is a little boilerplate up front in exchange for one authoritative, documented, validated settings object. The main pitfall is forgetting @Validated or @Valid, which silently skips the checks. This approach is worth reaching for as soon as configuration becomes structured or safety-critical.


Related snips

graphql
type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
    createdAt: String!

GraphQL API with Spring Boot

java graphql spring-boot
by David Kumar 3 tabs
ruby
class SignupForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :account_name, :string
  attribute :email, :string

Shallow Controller, Deep Params: Form Object Pattern

rails activemodel form-object
by codesnips 3 tabs
java
package com.example.starter.config;

import com.example.starter.properties.CustomProperties;
import com.example.starter.service.CustomService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

Custom Spring Boot starters

java spring-boot starter
by David Kumar 4 tabs
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Form Validation Example</title>
  <style>

HTML forms with validation and accessibility

html forms validation
by Alex Chang 1 tab
java
package com.example.demo.config;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

Messaging with Apache Kafka

java kafka messaging
by David Kumar 3 tabs
typescript
import axios from 'axios';

export type NormalizedErrors = {
  fields: Record<string, string>;
  formLevel: string | null;
};

Frontend: normalize and display server validation errors

ux typescript react
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Bind and Validate Grouped App Settings with @ConfigurationProperties in Spring Boot — share card
Link copied