Skip to content
Spring Boot sb production 4 min read

Secrets & Vault

Database passwords, API keys, and signing keys are secrets — leaking them is a breach. Yet the path of least resistance is to drop them straight into application.yml, where they end up in Git history, build artifacts, and log dumps. This page covers why that is dangerous and the spectrum of safer options, from environment variables to a dedicated secret manager like HashiCorp Vault, integrated through Spring Cloud Vault.

The problem with plaintext secrets

A secret committed to application.properties is exposed in every clone of the repository, survives in Git history even after deletion, gets baked into Docker image layers, and frequently shows up in stack traces or config-dump endpoints. Rotating it means a code change and redeploy. Anyone with read access to the repo or the image has the keys.

# DON'T do this
spring:
  datasource:
    password: SuperSecret123   # now in Git forever

Step one: environment variables

The minimum bar is to externalize secrets out of source and inject them at runtime. Spring Boot’s relaxed binding maps SPRING_DATASOURCE_PASSWORD to spring.datasource.password, so a placeholder reads from the environment:

spring:
  datasource:
    password: ${SPRING_DATASOURCE_PASSWORD}

This is a real improvement — the secret is no longer in the repo — but env vars are still static, visible to anyone who can inspect the process or container, hard to rotate without a restart, and not audited. See Externalized Configuration for the full property-resolution order.

Step two: a secret manager

A secret manager stores secrets encrypted, controls who can read each one, audits every access, and supports rotation and dynamic (short-lived) credentials. The application authenticates to the manager at startup and fetches what it needs.

ApproachEncrypted at restAccess controlRotationAudit logDynamic secrets
Plaintext in configNoNoManual + redeployNoNo
Environment variablesNo (process-visible)OS/orchestratorRestartNoNo
Kubernetes Secretsbase64 (optional encryption)RBACManualLimitedNo
HashiCorp VaultYesPolicies/tokensYesYesYes
AWS Secrets ManagerYesIAMYes (managed)CloudTrailPartial

Spring Cloud Vault

HashiCorp Vault is a popular secret manager. Spring Cloud Vault fetches secrets at startup and exposes them as ordinary Spring properties — your code uses @Value or @ConfigurationProperties with no idea the value came from Vault.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>

Modern Spring Boot pulls Vault in through config import rather than legacy bootstrap. Add it in application.yml:

spring:
  config:
    import: "vault://"
  cloud:
    vault:
      uri: https://vault.example.com:8200
      authentication: TOKEN
      token: ${VAULT_TOKEN}
      kv:
        enabled: true
        backend: secret
        default-context: orders

Note: The spring.config.import=vault:// line is required in Spring Boot 2.4+. Without it, the Vault config is not loaded. You can make it optional with optional:vault:// so the app still starts in environments without Vault (e.g. local dev).

Reading KV secrets

With the KV (key/value) engine enabled, write a secret in Vault:

vault kv put secret/orders \
    spring.datasource.password=s3cr3t \
    app.api-key=ak_live_123

Spring Cloud Vault maps each key directly onto a Spring property. So this just works with no extra wiring:

spring:
  datasource:
    password: ${spring.datasource.password}   # resolved from Vault KV

Or bind a group of secrets with type-safe @ConfigurationProperties:

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "app")
public record AppSecrets(String apiKey) {}

Output (startup log confirms the source):

Located property source: [VaultConfigDataResource path='secret/orders']
Fetched 2 secrets from Vault backend 'secret'
Started OrdersApplication in 1.9 seconds

Alternatives

  • AWS Secrets Manager / Parameter Store — managed, IAM-controlled. Spring Cloud AWS exposes them via the same spring.config.import mechanism (aws-secretsmanager://).
  • Kubernetes Secrets — mounted as env vars or files; simplest in a k8s deployment but only base64-encoded unless you enable encryption at rest. See Kubernetes Deployment.
  • Jasypt — encrypts values inside the config file (ENC(...)) using a master password supplied at runtime. Useful when you can’t run a secret manager, but it just shifts the problem to protecting the master key.
# Jasypt: encrypted value in the file, decrypted with a runtime master password
spring:
  datasource:
    password: ENC(7sf9aB3kQ2lP...)

Warning: Jasypt only moves the secret problem — you still must distribute and protect the jasypt.encryptor.password master key securely. A dedicated secret manager is preferable whenever one is available.

Best Practices

  • Never commit secrets to source control or bake them into images.
  • Prefer a secret manager (Vault, AWS Secrets Manager) over static env vars for audit, rotation, and access control.
  • Use spring.config.import=vault:// (mark it optional: for local dev) — bootstrap is legacy.
  • Favor dynamic, short-lived credentials over long-lived static ones when the backend supports them.
  • Rotate secrets regularly and ensure the app can pick up rotated values without a code change.
Last updated June 13, 2026
Was this helpful?