Available Hire Me
← All Writing AWS

Centralised Configuration with AWS Parameter Store and Spring Cloud

How to centralise Spring Boot configuration in AWS Parameter Store using Spring Cloud AWS — bootstrap loading, environment-specific paths, secrets, and IAM least-privilege setup.

Hardcoded config files and environment variables scattered across EC2 instances, ECS task definitions, and Lambda functions are a maintenance problem. When a database password rotates or an API endpoint changes, you’re hunting through deployment configs across multiple services. AWS Parameter Store with Spring Cloud AWS solves this: your application fetches its config from a central, versioned, audited store at startup — and you change one value instead of redeploying every service.

What Parameter Store gives you

AWS Systems Manager Parameter Store stores key-value pairs with three types:

  • String — plain text values (endpoints, feature flags, non-sensitive config)
  • SecureString — KMS-encrypted values (passwords, API keys, tokens)
  • StringList — comma-separated lists

Every write is versioned and audited via CloudTrail. You can read the history of any parameter, see who changed it and when, and roll back. This is something environment variables simply cannot give you.

Spring Cloud AWS dependency

Add the Spring Cloud AWS BOM and the Parameter Store starter:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.awspring.cloud</groupId>
      <artifactId>spring-cloud-aws-dependencies</artifactId>
      <version>3.1.1</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>io.awspring.cloud</groupId>
    <artifactId>spring-cloud-aws-starter-parameter-store</artifactId>
  </dependency>
</dependencies>

Spring Cloud AWS 3.x is the current generation — it replaces the older spring-cloud-starter-aws packages and is actively maintained against Spring Boot 3.x.

Parameter naming convention

Parameter Store uses a hierarchical path structure. Spring Cloud AWS maps this directly to Spring Environment properties. The convention is:

/application/{app-name}/{spring-profile}/{property-path}

For a service called order-service with a prod profile:

/application/order-service/prod/spring.datasource.url
/application/order-service/prod/spring.datasource.password
/application/order-service/prod/external.payments.api-key

Spring Cloud AWS reads parameters from two paths by default:

  1. /config/{app-name}/ — app-specific config
  2. /config/application/ — shared config across all services

Both paths are merged into the Spring Environment. Shared parameters (common database connection pool settings, shared feature flags) go in /config/application/; service-specific values go in /config/{app-name}/.

Configuring Spring Cloud AWS

In application.yml:

spring:
  application:
    name: order-service
  config:
    import: "aws-parameterstore:"

aws:
  paramstore:
    prefix: /config
    default-context: application
    profile-separator: "_"
    enabled: true

The spring.config.import: "aws-parameterstore:" line is the Spring Boot 3.x approach — it triggers the Parameter Store property source during the config import phase, before the application context loads. This replaces the older bootstrap context approach.

Environment-specific paths

With spring.profiles.active=prod, Spring Cloud AWS will look for parameters in:

/config/order-service/         # base app config
/config/order-service_prod/    # prod overrides
/config/application/           # shared base
/config/application_prod/      # shared prod overrides

The profile-specific path takes precedence. To override the database URL for prod only:

# Shared (all environments)
/config/application/spring.datasource.hikari.maximum-pool-size  →  10

# Prod override
/config/application_prod/spring.datasource.hikari.maximum-pool-size  →  50

Your application code sees spring.datasource.hikari.maximum-pool-size as 50 in prod and 10 everywhere else — no code changes, no redeploy.

Using SecureString for secrets

Sensitive values should be stored as SecureString. Spring Cloud AWS decrypts them transparently using the KMS key associated with the parameter — your application receives the plaintext value:

Parameter name:  /config/order-service_prod/spring.datasource.password
Type:            SecureString
KMS key:         alias/order-service-config
Value:           (encrypted at rest)

From the application’s perspective, ${spring.datasource.password} resolves to the decrypted value. The KMS call happens during startup, not on every property access.

IAM policy — least privilege

The application’s IAM role needs only the permissions it actually uses:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:GetParametersByPath",
        "ssm:GetParameters",
        "ssm:GetParameter"
      ],
      "Resource": [
        "arn:aws:ssm:eu-west-1:123456789012:parameter/config/order-service*",
        "arn:aws:ssm:eu-west-1:123456789012:parameter/config/application*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "kms:Decrypt",
      "Resource": "arn:aws:kms:eu-west-1:123456789012:key/your-kms-key-id"
    }
  ]
}

Scoping the Resource to the path prefix for your service means a compromised service can only read its own parameters — not every parameter in your account.

Injecting parameters in your application

Once loaded, parameters are standard Spring properties. Inject them exactly as you would any other config value:

@ConfigurationProperties(prefix = "external.payments")
@Validated
public record PaymentsConfig(
    @NotBlank String apiKey,
    @NotBlank String baseUrl,
    @Min(1) int timeoutSeconds
) {}
@Service
@RequiredArgsConstructor
public class PaymentsClient {

    private final PaymentsConfig config;

    public PaymentResponse charge(ChargeRequest request) {
        return restClient.post()
            .uri(config.baseUrl() + "/charge")
            .header("Authorization", "Bearer " + config.apiKey())
            .body(request)
            .retrieve()
            .body(PaymentResponse.class);
    }
}

The config record receives external.payments.api-key, external.payments.base-url, and external.payments.timeout-seconds from Parameter Store, validated at startup. If any are missing or blank, the application fails fast before it starts serving traffic.

Local development without AWS

For local development, you don’t want to hit real Parameter Store on every restart. Set aws.paramstore.enabled=false in your application-local.yml profile and provide values via a local application-local.yml or environment variables. Spring Cloud AWS will skip the Parameter Store fetch entirely:

# application-local.yml
aws:
  paramstore:
    enabled: false

external:
  payments:
    api-key: local-test-key
    base-url: http://localhost:8099
    timeout-seconds: 5

In CI, use an IAM role with Parameter Store access so integration tests read the same parameter paths as production. This catches missing or misconfigured parameters before they reach prod.

Refreshing config at runtime

Spring Cloud AWS integrates with Spring Cloud’s @RefreshScope. If you annotate a bean with @RefreshScope and POST to /actuator/refresh, Spring Cloud AWS will re-fetch parameters from Parameter Store and rebind them:

@Component
@RefreshScope
@ConfigurationProperties(prefix = "feature")
public record FeatureFlags(boolean newCheckoutFlow, boolean betaSearch) {}

For most config (database credentials, API keys), a restart is safer and simpler than a live refresh. @RefreshScope is most useful for low-risk config like feature flags where you want to toggle values without a deployment.

If you’re building a multi-service Spring Boot system on AWS and want to talk through centralising config — or securing the IAM setup around Parameter Store — get in touch.

Samuel Jackson

Samuel Jackson

Senior Java Back End Developer & Contractor

Senior Java Back End Developer — Betfair Exchange API specialist, Spring Boot, AWS, and event-driven architecture. 20+ years delivering high-performance systems across betting, finance, energy, retail, and government. Available for Java contracting.