Back to Blog
spring-bootconfigurationsecuritydevopsjavacloud

Spring Boot Configuration Management: Profiles, Secrets, and Externalized Config (2026)

Hardcoded config is a security incident waiting to happen. Learn Spring Boot profiles, environment variables, Spring Cloud Config, HashiCorp Vault integration, and config validation.

J

JOptimize Team

May 28, 2026· 8 min read

Configuration management is where many Spring Boot apps quietly accumulate security debt. Database passwords in application.properties committed to Git. JWT secrets hardcoded in configuration files. API keys that change in production but require a redeploy. These are not just inconveniences — they are security vulnerabilities.


The Property Source Hierarchy

Spring Boot resolves properties in this order (higher number = higher priority):

1. Default properties (SpringApplication.setDefaultProperties)
2. @PropertySource annotations
3. application.properties / application.yml
4. application-{profile}.properties
5. OS environment variables
6. Java system properties (-Dkey=value)
7. Command-line arguments (--key=value)

Environment variables (5) override application.properties (3) — this is the foundation of 12-factor app configuration.


Profiles: Dev vs Staging vs Production

src/main/resources/
  application.properties          # Shared across all environments
  application-dev.properties      # Local development
  application-staging.properties  # Staging environment
  application-prod.properties     # Production
# application.properties — shared, safe to commit spring.application.name=order-service server.port=8080 spring.jpa.open-in-view=false # application-dev.properties — dev values spring.datasource.url=jdbc:postgresql://localhost:5432/orders_dev spring.jpa.hibernate.ddl-auto=create-drop logging.level.com.myapp=DEBUG # application-prod.properties — references env vars, no secrets spring.datasource.url=${DATABASE_URL} spring.datasource.username=${DATABASE_USER} spring.datasource.password=${DATABASE_PASSWORD} logging.level.com.myapp=WARN

Activate profile:

# Via env var (recommended for containers) SPRING_PROFILES_ACTIVE=prod java -jar app.jar # Via system property java -Dspring.profiles.active=prod -jar app.jar

@ConfigurationProperties: Type-Safe Config

@ConfigurationProperties(prefix = "app.payment") @Validated public record PaymentConfig( @NotBlank String apiKey, @NotBlank String webhookSecret, @URL String callbackUrl, @Min(1) @Max(10) int maxRetries, @DurationUnit(ChronoUnit.SECONDS) Duration timeout ) {}
# application.properties app.payment.api-key=${PAYMENT_API_KEY} app.payment.webhook-secret=${PAYMENT_WEBHOOK_SECRET} app.payment.callback-url=https://myapp.com/webhooks/payment app.payment.max-retries=3 app.payment.timeout=30s
// Enable binding @SpringBootApplication @ConfigurationPropertiesScan public class Application { ... } // Use in service @Service @RequiredArgsConstructor public class PaymentService { private final PaymentConfig config; // Injected, validated at startup }

With @Validated, the app fails to start if PAYMENT_API_KEY is missing or blank — rather than failing at runtime when the first payment is attempted.


Secrets Management with HashiCorp Vault

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-vault-config</artifactId> </dependency>
# bootstrap.properties (loaded before application.properties) spring.cloud.vault.uri=http://vault:8200 spring.cloud.vault.token=${VAULT_TOKEN} spring.cloud.vault.kv.enabled=true spring.cloud.vault.kv.default-context=order-service
# Store secrets in Vault vault kv put secret/order-service \ database.password=super-secret-db-pass \ jwt.secret=my-256-bit-secret

Spring Cloud Vault fetches secrets at startup and injects them as properties — no secret ever touches a config file or environment variable. Rotation is instant: update Vault, restart the app.


Kubernetes Secrets Integration

For Kubernetes without Vault, use mounted secrets:

# kubernetes deployment.yaml env: - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: db-secret key: password - name: JWT_SECRET valueFrom: secretKeyRef: name: jwt-secret key: secret

The Kubernetes secret is injected as an environment variable — Spring Boot picks it up via ${DATABASE_PASSWORD} in properties.


Spring Cloud Config Server

For centralized configuration across many microservices:

# Service A — bootstrap.properties spring.config.import=configserver:http://config-server:8888 spring.application.name=order-service

The Config Server serves properties from a Git repository, with per-service and per-profile overrides. Change a property, push to Git — all service instances refresh without restart (with Spring Cloud Bus + RabbitMQ/Kafka).


Detecting Hardcoded Secrets

// DANGEROUS — secret in code, committed to Git @Value("eyJhbGciOiJIUzI1NiJ9.secret") private String jwtSecret; // SAFE — from environment variable @Value("${JWT_SECRET}") private String jwtSecret;

Add a pre-commit hook or CI step to detect hardcoded secrets:

# .pre-commit-config.yaml repos: - repo: https://github.com/Yelp/detect-secrets rev: v1.4.0 hooks: - id: detect-secrets

Common Mistakes to Avoid

  • Committing application-prod.properties with real values — prod config belongs in environment variables or a secrets manager, never in Git
  • Using spring.jpa.hibernate.ddl-auto=create-drop in prod — this drops and recreates your schema on startup; use validate in prod and Flyway for migrations
  • No config validation at startup — without @Validated on @ConfigurationProperties, missing secrets cause runtime failures instead of clean startup errors
  • Logging config values@ConfigurationProperties records in toString() will log all values including secrets; override toString() to mask sensitive fields

Summary

Production-grade Spring Boot configuration uses profiles to separate environments, externalized config via environment variables for secrets, @ConfigurationProperties with @Validated for type-safe early failure, and a secrets manager (Vault or Kubernetes Secrets) for credentials. The rule: if it's a secret, it never touches a config file committed to version control.


Detect Configuration Security Issues

JOptimize flags hardcoded credentials in @Value annotations, ddl-auto=create-drop in non-dev profiles, and missing validation on @ConfigurationProperties classes.

Audit your configuration security before your credentials leak.

Want to go deeper?

Master Spring Boot, security, and Java performance with hands-on courses.

Detect issues in your project

JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.