spring-cloud-aws icon indicating copy to clipboard operation
spring-cloud-aws copied to clipboard

Configuration parameters not correctly loaded from AWS Parameter Store when profile-specific config files are present

Open mrnst opened this issue 6 months ago • 7 comments

Type: Bug

Component: Parameter Store

Describe the bug

My Spring Boot application uses Spring Cloud AWS to access AWS Parameter Store. The following versions are used:

  • Spring Boot 3.4.6
  • Spring Cloud 2024.0.1
  • Spring Cloud AWS 3.3.1

The application has 2 configuration files:

  • application.yml for basic configuration
  • application-aws.yml for profile-specific configuration for a profile named "aws"

At runtime, the environment variable SPRING_PROFILES_ACTIVE is set to the value "aws".

The file application.yml contains a general parameter named foo:

spring.config.import:
  - optional:aws-parameterstore:/myapplication

foo: Foo

The file application-aws.yml contains an additional parameter named foo-aws:

foo-aws: Foo-AWS

At runtime, both parameters are supposed to be overwritten with new values from AWS Parameter Store :

  • /myapplication/foo := Foo-ParameterStore
  • /myapplication/foo-aws := Foo-AWS-ParameterStore

However, at runtime, the application still sees the original values from the 2 static configuration files. Neither of the values defined in AWS Parameter Store is injected.

Moreover, the behaviour seems to depend on the location of the spring.config.import setting shown above:

  • If the spring.config.import is configured only in application.yml (as shown above), then the behaviour is as described.
  • If the spring.config.import is configured both in application.yml and in application-aws.yml, then the behaviour is also as described.
  • However, if the spring.config.import is only in application-aws.yml (but not in application.yml), then the parameter foo-aws (originating from application-aws.yml) is overwritten with the value from AWS parameter store, but foo (originating from application.yml) is still not overwritten.

Expected behaviour

It should be possible to overwrite parameters from all (general or profile-specific) configuration files with values from AWS Parameter Store.

Additional information

This issue is probably related to #1224, but provides some additional information regarding the location of spring.config.import.

mrnst avatar Jun 05 '25 13:06 mrnst

Hey @mrnst ,

What you are seeing is actually correct way of determining properties since file is loaded first and has priority over spring.config.import meaning any value in application.yaml has priority over things that is imported.

Link to Spring Boot Docs

Config data files are considered in the following order:

[Application properties](https://docs.spring.io/spring-boot/reference/features/external-config.html#features.external-config.files) packaged inside your jar (application.properties and YAML variants).

[Profile-specific application properties](https://docs.spring.io/spring-boot/reference/features/external-config.html#features.external-config.files.profile-specific) packaged inside your jar (application-{profile}.properties and YAML variants).

[Application properties](https://docs.spring.io/spring-boot/reference/features/external-config.html#features.external-config.files) outside of your packaged jar (application.properties and YAML variants).

[Profile-specific application properties](https://docs.spring.io/spring-boot/reference/features/external-config.html#features.external-config.files.profile-specific) outside of your packaged jar (application-{profile}.properties and YAML variants).

As you can see our parameter store values are under 3 and 4. Outside packaged jar meaning application.properties which are in resources take higher priority. Hope this helped to explain it.

There is good explanation by Phillip Webb about loading and describes how ConfigResources are built in a tree structure and loaded. This also describes why profile loading is not changing value of application.yaml -> Issue

MatejNedic avatar Jun 07 '25 19:06 MatejNedic

Thanks @MatejNedic, your explanation was helpful to understand how the location of the spring.config.import statement influences the resulting configuration.

But I'm still not convinced that the import from AWS Parameter Store works correctly.

My updated application setup is now as follows:

application.yml is:

foo: foo_from_application.yml

application-aws.yml is:

spring.config.import:
  - optional:aws-parameterstore:/myapplication

foo: foo_from_application-aws.yml

foo-aws: foo-aws_from_application-aws.yml

At runtime, the environment variable SPRING_PROFILES_ACTIVE is set to the value "aws".

The configuration values stored in AWS Parameter Store are:

  • /myapplication/foo := foo_from_ParameterStore
  • /myapplication/foo-aws := foo-aws_from_ParameterStore

When I run my application with this setup (and using @Value to inject configured values), my observation is:

  • The value of foo-aws is: foo-aws_from_ParameterStore (as in AWS Parameter Store)
  • The value of foo is: foo_from_application-aws.yml (as in application-aws.yml)

The observed value of foo is not what I would expect from this description of how importing external config files works: Values from the imported dev.properties will take precedence over the file that triggered the import.

I cross-checked: If in application-aws.yml, additional configuration is imported from a file instead, using e.g.

spring.config.import:
  - optional:file:/path/to/config.yml

then my application sees the values in /path/to/config.yml, as I would expect.

mrnst avatar Jun 10 '25 15:06 mrnst

Hey @mrnst ,

Sorry on late reply. Spring Docs are worded little bit weird and I have not worded any better sorry.

When I run my application with this setup (and using @Value to inject configured values), my observation is:

The value of foo-aws is: foo-aws_from_ParameterStore (as in AWS Parameter Store) The value of foo is: foo_from_application-aws.yml (as in application-aws.yml)

It is other way around right? foo-was: is foo_from_application-aws.yml and foo is foo-aws_from_ParameterStore

If you mean that than yes. When loading we are not taking profile into account meaning we are loading of following in your example:

/myapplication/*

Now if we you have /myapplication/foo and /myapplication/foo-aws We will load it and save as foo-aws: value foo: otherValue

foo-aws won't be resolved to foo and should be activated on profile AWS. I am not sure if you meant that so please correct me!

If you check Spring Docs when importing something with spring.config.import it should take priority over anything.

I have tried locally with 3.3.1 version and so far I am seeing exact that behaviour. Even for profile spring.config.import loaded parameter take priority. I have used @ConfigurationProperties for this and simple GetMapping.

Would you be so kind to provide me sample please so I check it out? Since I cannot reproduce it.

MatejNedic avatar Jun 14 '25 20:06 MatejNedic

Hello @MatejNedic,

you can find the requested sample in this repository.

I run this application in AWS ECS with the following parameter values in AWS Parameter Store:

Parameter Name Value
/mrnst/playground/foo foo_from_ParameterStore
/mrnst/playground/foo-aws foo-aws_from_ParameterStore

The profile "aws" is activated.

The application produces the following output:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.4.6)
2025-06-17T10:19:59.382+02:00  INFO 1 --- [           main] mrnst.playground.PlaygroundApplication   : Starting PlaygroundApplication v1.0-SNAPSHOT using Java 21.0.7 with PID 1 (/application/BOOT-INF/classes started by root in /application)
2025-06-17T10:19:59.481+02:00  INFO 1 --- [           main] mrnst.playground.PlaygroundApplication   : The following 1 profile is active: "aws"
2025-06-17T10:20:00.281+02:00  INFO 1 --- [           main] .a.c.a.c.p.ParameterStorePropertySources : Loading property from AWS Parameter Store with name: /mrnst/playground, optional: true
2025-06-17T10:20:00.282+02:00 DEBUG 1 --- [           main] i.a.c.p.ParameterStorePropertySource     : Populating property retrieved from AWS Parameter Store: .foo
2025-06-17T10:20:00.282+02:00 DEBUG 1 --- [           main] i.a.c.p.ParameterStorePropertySource     : Populating property retrieved from AWS Parameter Store: .foo-aws
2025-06-17T10:20:17.091+02:00  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2025-06-17T10:20:17.282+02:00  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-06-17T10:20:17.283+02:00  INFO 1 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.41]
2025-06-17T10:20:17.593+02:00  INFO 1 --- [           main] o.a.c.c.C.[.[.[/mrnst/playground/api]    : Initializing Spring embedded WebApplicationContext
2025-06-17T10:20:17.676+02:00  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 17390 ms
2025-06-17T10:20:25.278+02:00  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint beneath base path '/actuator'
2025-06-17T10:20:27.384+02:00  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/mrnst/playground/api'
2025-06-17T10:20:27.582+02:00  INFO 1 --- [           main] mrnst.playground.PlaygroundApplication   : Started PlaygroundApplication in 66.796 seconds (process running for 72.597)
2025-06-17T10:20:30.011+02:00  INFO 1 --- [   scheduling-1] mrnst.playground.ScheduledService        : Starting scheduled task
2025-06-17T10:20:30.011+02:00  INFO 1 --- [   scheduling-1] mrnst.playground.ScheduledService        : foo     : foo_from_application-aws.yml
2025-06-17T10:20:30.015+02:00  INFO 1 --- [   scheduling-1] mrnst.playground.ScheduledService        : foo-aws : foo-aws_from_ParameterStore
2025-06-17T10:20:30.015+02:00  INFO 1 --- [   scheduling-1] mrnst.playground.ScheduledService        : Scheduled task finished

The output shows that both properties foo and foo-aws are retrieved from AWS parameter store. Therefore, I would expect the property foo to have the value foo_from_ParameterStore.

I hope this helps to reproduce.

mrnst avatar Jun 17 '25 09:06 mrnst

Hey @mrnst ,

Thanks so much on sample and sorry on late reply again I was not available.

When loading in your yaml you are missing / on then end since currently your variables are stored with a .foo You can see this in logs as well.

spring.config.import:
  - optional:aws-parameterstore:/mrnst/playground

To fix your problem simply add slash at the end. This will load by the path since we are loading by the path and it ends with a / ->

spring.config.import:
  - optional:aws-parameterstore:/mrnst/playground/

MatejNedic avatar Jul 02 '25 19:07 MatejNedic

Thank you @MatejNedic. I was able to solve my configuration problem with your explanations in this thread.

I'm aware that everything you explained is already contained in the documentation. But maybe a little hint on significance of the trailing "/" in spring.config.import statement for AWS Parameter Store would be helpful to address uncareful readers like me.

This issue can be closed.

mrnst avatar Jul 07 '25 07:07 mrnst

Happy to help!

I will leave this issue opened. We can add in logs warning about trailing slash.

MatejNedic avatar Jul 10 '25 19:07 MatejNedic