Greetings Spring Boot. I ran into an unexpected results with the following setup with config loading. Let's consider we have the following application.yml
file.
test:
environment: test
---
spring:
config:
activate:
on-profile: profileA
test:
environment: prod
---
spring:
config:
activate:
on-profile: profileB
import:
- application/${test.environment}.yml
otherProp: ${test.environment}
And we have application/prod.yml
with the following contents:
myTestProp: testProp/prod
And application/test.yml
with the following contents:
myTestProp: testProp/test
And finally we have the following test that will activate profileB
and assert on expected results.
@ActiveProfiles(profiles = "profileB")
@SpringBootTest
public class ProfileBPropsTest {
@Autowired
private Environment env;
@Test
public void Test() {
assertEquals("test", env.getProperty("test.environment", String.class));
assertEquals("test", env.getProperty("otherProp", String.class));
assertEquals("testProp/test", env.getProperty("myTestProp", String.class));
}
@Configuration
class TestConfig {}
And we have the following test to activate both profiles profileA
and profileB
and assert on expected results.
@ActiveProfiles(profiles = {"profileA", "profileB"})
@SpringBootTest
public class ProfileABPropsTest {
@Autowired
private Environment env;
@Test
public void Test() {
assertEquals("prod", env.getProperty("test.environment", String.class));
assertEquals("prod", env.getProperty("otherProp", String.class));
assertEquals("testProp/prod", env.getProperty("myTestProp", String.class));
}
@Configuration
class TestConfig {}
}
What I've observed is ProfileABPropsTest
passes expectedly. The file-order profile activation picks up the override for test.environment
in profileA
which is then used in profileB
to import prod.yml
as a property source.
However what fails is ProfileBPropsTest
. Only profileB
is active, so we assume the default value of test.environment=test
will cause test.yml
to be imported as a property source. Instead what we find is the value of myTestProp=testProp/prod
which would only happen if prod.yml
was loaded. But as we can see by the test, test.environment=test
, so how is it that we get this property in this scenario unless the expression ${test.environment}
is pulling in an incorrect value that should technically only happen if profileA
is activated.
I cannot explain this from reading the Spring documentation. For context, I am seeing this in spring boot 2.5.x.
Comment From: tauparticle
I uploaded the code to reproduce this problem here. https://github.com/tauparticle/SpringBootPropLoadingDemo
Comment From: philwebb
@tauparticle https://github.com/tauparticle/SpringBootPropLoadingDemo gives a 404 for me. Can you double check the link is correct.
Comment From: tauparticle
@philwebb apologies. I think it was made private originally. It should work now.
Comment From: philwebb
Thanks. The problem looks somewhat related to #23020. The placeholder resolver logic isn't working with contributors that have Kind.UNBOUND_IMPORT
.
Comment From: philwebb
@tauparticle I've pushed a fix for this, but I'm afraid it's not going to allow you to import configuration in the way you're trying to do it. Placeholder resolution should have always failed when you try to reference a property in an inactive document. This was an intentional design decision that we made when we introduced spring.config.import
support. The idea was to prevent some of the issues that make application.yaml
processing hard to reason about.
I would suggest changing your YAML so that the imports are directly declared, rather than using placeholders. For example:
---
spring:
config:
activate:
on-profile: profileA
import:
- application/prod.yml
---
spring:
config:
activate:
on-profile: profileB
import:
- application/test.yml
Comment From: tauparticle
Thanks for the clarification @philwebb!