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!