When the property exists in env everything works fine. Once there is no property exist, it can not get the default value via either @ConfigurationProperties, @Value or Environment.

Maybe it is nice to make the behavior consistency between properties and placeholder have same name or not.


Sample to reproduce.

application.properties

k8s.sidecar.inject=${k8s.sidecar.inject:defaultVal}
@SpringBootApplication
public class ReproductionApplication {


    public static void main(String[] args) {
        SpringApplication.run(ReproductionApplication.class, args);
    }

    @Autowired
    Environment environment;

    @PostConstruct
    public void test() {
        final String name = environment.getProperty("k8s.sidecar.inject");
    }
}

...
Caused by: java.lang.IllegalArgumentException: Circular placeholder reference 'k8s.sidecar.inject:defaultVal' in property definitions
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:147) ~[spring-core-5.3.28.jar:5.3.28]
    at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:168) ~[spring-core-5.3.28.jar:5.3.28]
    at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126) ~[spring-core-5.3.28.jar:5.3.28]
    at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:239) ~[spring-core-5.3.28.jar:5.3.28]
    at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:210) ~[spring-core-5.3.28.jar:5.3.28]
    at org.springframework.core.env.AbstractPropertyResolver.resolveNestedPlaceholders(AbstractPropertyResolver.java:230) ~[spring-core-5.3.28.jar:5.3.28]
    at org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.getProperty(ConfigurationPropertySourcesPropertyResolver.java:79) ~[spring-boot-2.7.13.jar:2.7.13]
    at org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.getProperty(ConfigurationPropertySourcesPropertyResolver.java:60) ~[spring-boot-2.7.13.jar:2.7.13]
    at org.springframework.core.env.AbstractEnvironment.getProperty(AbstractEnvironment.java:594) ~[spring-core-5.3.28.jar:5.3.28]
    at com.example.jasyptspringbootreproduct.ReproductionApplication.test(ReproductionApplication.java:23) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389) ~[spring-beans-5.3.28.jar:5.3.28]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333) ~[spring-beans-5.3.28.jar:5.3.28]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157) ~[spring-beans-5.3.28.jar:5.3.28]
    ... 18 common frames omitted

Comment From: wilkinsona

Thanks for the report but I'm not sure what you expect to be done here. I don't think the cycle can be broken by using the default value as we have no way of knowing that's what the user would want to happen. Furthermore, if the cycle involved several properties, each with a default value, how would the default value to use be chosen?

PropertyPlaceholderHelper is part of Spring Framework. If you feel strongly about this and have answers to the questions above, feel free to open a Spring Framework issue but I think it's unlikely that they will be willing to make a change here.

Comment From: Koooooo-7

Thanks for the report but I'm not sure what you expect to be done here. I don't think the cycle can be broken by using the default value as we have no way of knowing that's what the user would want to happen. Furthermore, if the cycle involved several properties, each with a default value, how would the default value to use be chosen?

PropertyPlaceholderHelper is part of Spring Framework. If you feel strongly about this and have answers to the questions above, feel free to open a Spring Framework issue but I think it's unlikely that they will be willing to make a change here.

Hi @wilkinsona , I think the cycle error stack get a little bit misleading, since it should not have this Circular placeholder reference actually. And there is missing consistency. i.e

Scenario 1:

Contains my_property/my_property_env in env.

Works fine:

my_property=${my_property:defaultVal}          // why there is no cycle throw but try to get the env?

Works fine:

my_property=${my_property_env:defaultVal}

Scenario 2:

Not contains my_property/my_property_env in env.

Broken by the Cycle throw:

my_property=${my_property:defaultVal}         // why there try to get env failed but not use the default val?

Works fine:

my_property=${my_property_env:defaultVal}     // get env failed but do use the default val

Scenario 3:

Not contains my_property/my_property_env in env.

Broken by the Cycle throw:

my_property=${my_property}

Throw with Could not resolve placeholder:

my_property=${my_property_env}        

IIUC, there is make no sense to say when the property with same name to the placeholder is special.

Besides, About the PropertyPlaceholderHelper, I do have a test on it as well. it works fine. SpringBoot Can not get property default value when the name of property same with placeholder.

Comment From: wilkinsona

If my_property is already defined in the environment in a property source that comes before the source that contains my_property=${my_property:defaultVal}, the value that contains the circular reference will never be used.

The following tests illustrate the importance of the order of the property sources:

@Test
void circularReferenceCausesIllegalArgumentException() {
    StandardEnvironment environment = new StandardEnvironment();
    environment.getPropertySources()
        .addFirst(new MapPropertySource("first", Map.of("my_property", "${my_property:defaultVal}")));
    assertThatIllegalArgumentException().isThrownBy(() -> environment.getProperty("my_property"));
}

@Test
void circularReferenceInLastPropertySourceIsHiddenByPropertyInEarlierPropertySource() {
    StandardEnvironment environment = new StandardEnvironment();
    MutablePropertySources propertySources = environment.getPropertySources();
    propertySources.addFirst(new MapPropertySource("first", Map.of("my_property", "first")));
    propertySources.addLast(new MapPropertySource("last", Map.of("my_property", "${my_property:last}")));
    assertThat(environment.getProperty("my_property")).isEqualTo("first");
}

@Test
void circularReferenceInFirstPropertySourceCausesIllegalArgumentExceptionIgnoringPropertyInLaterPropertySource() {
    StandardEnvironment environment = new StandardEnvironment();
    MutablePropertySources propertySources = environment.getPropertySources();
    propertySources.addFirst(new MapPropertySource("first", Map.of("my_property", "${my_property:first}")));
    propertySources.addLast(new MapPropertySource("last", Map.of("my_property", "${my_property:last}")));
    assertThatIllegalArgumentException().isThrownBy(() -> environment.getProperty("my_property"));
}

As I said above, this has nothing to do with Spring Boot as all of the code is part of Spring Framework. If you believe the behaviour could be improved, please open a Spring Framework issue that fully illustrates what you're trying to do and precisely describes how you think things should behave instead. If you have any further questions, please follow up on Stack Overflow. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements.

Comment From: Koooooo-7

I see, thx.