Problem statement

Hi Spring Team.

I'm evaluating Spring Boot 3.4.0-M3 and I ran into a regression in environment loading with tests using ApplicationContextRunner to construct programmatic application contexts and assert on their behavior.

The behavior that appears to be lost is the Environment no longer resolves environment placeholders in the following example.

Autoconfiguration to conditionally create a different implementation of an interface

@Configuration
public class AppConfiguration {

    @Bean
    public Animal testAnimal(Environment environment) {
        if (environment.getProperty("new.cat-mode.enabled", Boolean.class, false)) {
            return new Cat();
        }
        return new Dog();
    }
}

With an EnvironmentPostProcessor to conditionally load a new property source

public class PropertyEnvironmentPostProcessor implements EnvironmentPostProcessor {

    private static final String PROPERTY_SOURCE = "classpath:/custom-properties.properties";

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        if (environment.acceptsProfiles(Profiles.of("laptop"))) {
            try {
                environment.getPropertySources()
                        .addLast(new ResourcePropertySource("custom-prop", PROPERTY_SOURCE));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Which loads a single property with a backport placeholder property

new.cat-mode.enabled=${old.cat-mode.enabled:true}

And finally the test to validate this behavior

public class AppConfigurationTest {

    private final SpringApplication application = new SpringApplication();

    private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
            .withConfiguration(AutoConfigurations.of(AppConfiguration.class))
            .withInitializer(
                    ac -> new PropertyEnvironmentPostProcessor().postProcessEnvironment(ac.getEnvironment(), application));

    @Test
    void testCatMode() {
        contextRunner
                .withSystemProperties("spring.profiles.active=laptop",
                        "spring.application.name=apptest")
                .run(context -> {
                    assertThat(context).hasNotFailed();
                    assertThat(context).hasSingleBean(Cat.class);
                });
    }

    @Test
    void testDefaultMode() {
        contextRunner
                .run(context -> {
                    assertThat(context).hasNotFailed();
                    assertThat(context).hasSingleBean(Dog.class);
                });
    }
}

In Spring boot 3.3.x, this test passes. In 3.4.0-M3, it throws the following error with not being able to parse ${old.cat-mode.enabled:true} as a boolean.

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testAnimal' defined in com.example.demo.AppConfiguration: Failed to instantiate [com.example.demo.Animal]: Factory method 'testAnimal' threw exception with message: Failed to convert from type [java.lang.String] to type [java.lang.Boolean] for value [${old.cat-mode.enabled:true}]

Expectation

The expectation is there should be no changes in environment loading and placeholder resolution in this kind of test.

Proproducing this issue

I created the following Git repo that this test setup in 3.3.x and 3.4.0-M3.

Comment From: philwebb

Thanks for the reproducer, this looks like a Spring Framework regression. I've opened https://github.com/spring-projects/spring-framework/issues/33727

Comment From: iparadiso

Perfect. Thank you!