agentskills.codes
SP

spring-boot-config-refactor

Refactor a Spring Boot application's configuration from legacy application.properties and scattered @Value usage to structured YAML and immutable @ConfigurationProperties records. Use this when migrating messy configuration, standardizing property naming, introducing app-prefixed custom properties,

Install

mkdir -p .claude/skills/spring-boot-config-refactor && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/13395" && unzip -o skill.zip -d .claude/skills/spring-boot-config-refactor && rm skill.zip

Installs to .claude/skills/spring-boot-config-refactor

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Refactor a Spring Boot application's configuration from legacy application.properties and scattered @Value usage to structured YAML and immutable @ConfigurationProperties records. Use this when migrating messy configuration, standardizing property naming, introducing app-prefixed custom properties, or separating sensible defaults from local development overrides.
365 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)

About this skill

Spring Boot configuration refactor skill

Use this skill when the task is to analyze, restructure, and migrate application configuration in a Spring Boot codebase.

This skill is specifically optimized for repositories where:

  • application.properties has grown organically over time and is hard to understand.
  • Local development uses a dev profile and an additional config location such as devops/dev/config/.
  • Test and production deployments are configured externally through Kubernetes ConfigMap/Secret/sealed secrets rather than profile-specific packaged files.
  • Many settings are injected via @Value annotations.
  • The goal is to move to type-safe, immutable, record-based configuration using @ConfigurationProperties.

Desired target state

Follow these rules unless the user explicitly instructs otherwise:

  1. Use YAML instead of .properties for Spring configuration.
  2. Put application-specific properties under the app prefix.
  3. Keep standard Spring Boot / framework / library properties under their normal prefixes (for example spring.*, management.*, server.*, logging.*).
  4. Keep the same conceptual split between:
    • application.yml for minimal defaults and shared base configuration.
    • application-dev.yml for local development overrides.
    • application-test.yml for Spring Boot test scenarios when relevant.
  5. Migrate from scattered @Value usage to @ConfigurationProperties with immutable Java records.
  6. Inject configuration records as Spring beans via constructor injection.
  7. Add validation so invalid configuration fails fast during startup.
  8. Preserve the existing deployment model where production/test environment overrides come from external configuration, Kubernetes config maps, and secrets.
  9. Do not introduce environment-specific packaged config for production unless the user explicitly asks for it.

Key implementation principles

Configuration layout

  • src/main/resources/application.yml
    • Keep it minimal.
    • Include sensible defaults only.
    • Do not hardcode environment-specific secrets or infrastructure endpoints.
    • Use Spring Boot's native property keys directly for framework properties (spring.activemq.*, spring.data.redis.*, etc.) rather than creating intermediate "bridge" property layers (e.g. activemq.broker.urlspring.activemq.broker-url). Set localhost/empty defaults inline and let K8s ConfigMaps override with real values.
  • src/main/resources/application-dev.yml
    • Use for local dev defaults and local overrides only if that matches the repo's current practice.
    • If the project already loads external files from devops/dev/config/ using spring.config.additional-location, preserve that approach unless the user asks to change it.
  • src/test/resources/application-test.yml
    • Use when test-specific configuration is needed.

Property modeling

  • Group custom application properties into one or more records under a dedicated package such as:
    • ...config
    • ...config.properties
    • or the package already used by the project for application config
  • Prefer a small number of well-structured top-level property records over many tiny fragmented ones.
  • Use nested records to mirror the YAML hierarchy.
  • Keep names domain-oriented and self-explanatory.
  • Multi-module projects: each Gradle/Maven module that needs config should have its own @ConfigurationProperties record bound to its relevant subtree. For example, a main app module gets AppProperties (prefix = "app"), while an integration-foo module gets FooProperties (prefix = "app.integration.foo"). This keeps each module self-contained and avoids forcing a dependency on the root config record.

Example pattern:

package com.example.app.config.properties;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@Validated
@ConfigurationProperties(prefix = "app")
public record AppProperties(
    @Valid Feature feature,
    @Valid Integration integration
) {
    public record Feature(
        boolean enabled,
        @Min(1) int batchSize
    ) {}

    public record Integration(
        @NotBlank String baseUrl,
        @NotNull Timeout timeout
    ) {
        public record Timeout(
            @Min(1) int connectSeconds,
            @Min(1) int readSeconds
        ) {}
    }
}

Spring Boot wiring

  • Prefer @ConfigurationPropertiesScan on the main application class if it is not already in use.
  • Alternatively use @EnableConfigurationProperties(...) only when there is a strong repository-specific reason (for example, test @Configuration classes that don't trigger @SpringBootTest auto-scanning — see the test pitfalls section).
  • Use constructor injection exclusively.
  • Access values using record accessors, for example appProperties.integration().baseUrl().
  • Dependency: @Validated on @ConfigurationProperties records requires spring-boot-starter-validation. Check if it is already present before adding it.

Validation

  • Use @Validated on the configuration record.
  • Use Jakarta Bean Validation annotations such as:
    • @NotNull
    • @NotBlank
    • @Min
    • @Max
    • @Positive
    • @Valid for nested records
  • Ensure configuration errors fail fast at application startup.
  • @Valid on nested records does NOT enforce non-null: @Valid Security security means validation runs on the nested record's contents only if the record is non-null. If the property subtree is absent, the component is null and @Valid null passes silently. At runtime, accessing security().hashSalt() then throws NullPointerException. Use @NotNull @Valid if the component must always be present, or accept that the component may be null and guard access accordingly.

Handling existing @Value usage

Auditing @Value annotations

  • Search the codebase for all @Value annotations — but distinguish Lombok @Value from Spring @Value. Lombok @Value is a class-level annotation (generates getters, equals, hashCode). Spring @Value is org.springframework.beans.factory.annotation.Value and is used on fields/parameters for property injection. Filter for the Spring import, not just @Value text matches.
  • Also search for @Scheduled, @JmsListener, @KafkaListener, and other annotations that use ${...} placeholders in their attributes (e.g. @Scheduled(cron = "${my.cron}"), @JmsListener(destination = "${my.queue}")). These are property references that will break when legacy keys are removed, but they do not show up in a @Value search.

Classifying properties

Classify each occurrence:

  1. custom application property → migrate under app.*
  2. framework/library property → usually leave as framework config and inject a richer Spring abstraction where possible
  3. literal default with occasional override need → model as a typed property with a sensible default in YAML, or keep optional via constructor defaulting only if clearly justified

Migrating classes

  • Replace field injection and @Value injection with constructor-injected configuration beans.
  • When using Lombok @RequiredArgsConstructor for constructor injection, be aware that @RequiredArgsConstructor does not propagate @Qualifier annotations. If a bean needs both a @ConfigurationProperties record and a @Qualifier-annotated bean (e.g. a named RestClient), keep @Autowired @Qualifier on the qualified field (non-final) and make only the config properties field final for constructor injection.
  • Remove dead properties after migration.

Bridge properties and infrastructure config

Legacy configurations often have "bridge" properties — custom intermediate property keys that are referenced by Spring Boot's native property placeholders. For example:

# Bridge pattern (avoid in target state):
redis:
  host: 127.0.0.1
  port: 6379
spring:
  data:
    redis:
      host: "${redis.host}"
      port: "${redis.port}"

During migration:

  • Identify all bridge properties during the audit phase. Common examples: db.*spring.datasource.*, activemq.broker.*spring.activemq.*, redis.*spring.data.redis.*.
  • Replace bridges with direct values in application.yml. Set localhost/empty defaults for spring.activemq.*, spring.data.redis.*, etc. directly, and let K8s ConfigMaps override using the standard Spring property names (or their env var equivalents via Spring Boot's relaxed binding, e.g. SPRING_ACTIVEMQ_BROKER_URL).
  • If a bridge cannot be removed immediately (e.g. db.* properties are used to compose spring.datasource.url via ${db.server}:${db.port}/${db.name}), keep it temporarily and document it as a follow-up cleanup.

Property naming rules

  • Application-specific keys must move under app.
  • Avoid flat names when the domain has hierarchy.
  • Prefer this:
    • app.integration.certificate-service.base-url
    • app.jobs.cleanup.batch-size
  • Avoid this:
    • certificateServiceUrl
    • cleanupBatch

Secrets and external config

  • Never hardcode secrets in repository defaults.
  • Assume sensitive values should come from environment variables, sealed secrets, or external config in the devops repository.
  • Prefer env var placeholders for secrets where possible, for example app.security.hash-salt: "${HASH_SALT:}".
  • Preserve compatibility with spring.config.additional-location if the project already relies on it.

Required workflow — phased migration

When using this skill, follow this phased workflow. Each phase should result in a passing test suite and a separate commit. The user can review, test, and roll back each phase independently.

Phase 1: Audit the current state

Inspect at leas


Content truncated.

Search skills

Search the agent skills registry