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.
AWS Systems Manager Parameter Store stores key-value pairs with three types:
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.
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 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:
/config/{app-name}/ — app-specific config/config/application/ — shared config across all servicesBoth 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}/.
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.
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.
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.
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.
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.
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.
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.