diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java index c19a071911e0..7459a51725c7 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,8 +49,11 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.convert.ConversionService; +import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySources; import org.springframework.util.Assert; +import org.springframework.util.PropertyPlaceholderHelper; +import org.springframework.util.SystemPropertyUtils; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.springframework.validation.annotation.Validated; @@ -68,6 +71,8 @@ class ConfigurationPropertiesBinder { private static final String VALIDATOR_BEAN_NAME = EnableConfigurationProperties.VALIDATOR_BEAN_NAME; + private static final String IGNORE_UNRESOLVABLE_PLACEHOLDER_PROPERTY = "spring.configurationproperties.ignore-unresolvable-placeholders"; + private final ApplicationContext applicationContext; private final PropertySources propertySources; @@ -191,7 +196,16 @@ private Iterable getConfigurationPropertySources() } private PropertySourcesPlaceholdersResolver getPropertySourcesPlaceholdersResolver() { - return new PropertySourcesPlaceholdersResolver(this.propertySources); + return new PropertySourcesPlaceholdersResolver(this.propertySources, getPropertyPlaceholderHelper()); + } + + private PropertyPlaceholderHelper getPropertyPlaceholderHelper() { + Environment environment = this.applicationContext.getEnvironment(); + boolean ignoreUnresolvablePlaceholders = environment.getProperty(IGNORE_UNRESOLVABLE_PLACEHOLDER_PROPERTY, + Boolean.class, true); + return new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, + SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, + SystemPropertyUtils.ESCAPE_CHARACTER, ignoreUnresolvablePlaceholders); } private List getConversionServices() { diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index ca6e639151ea..a0d6335bc6c8 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -475,6 +475,13 @@ "description": "Config file name.", "defaultValue": "application" }, + { + "name": "spring.configurationproperties.ignore-unresolvable-placeholders", + "type": "java.lang.Boolean", + "sourceType": "org.springframework.boot.context.properties.ConfigurationPropertiesBinder", + "description": "Whether unresolvable placeholders should be ignored or trigger an exception during the binding of configuration properties", + "defaultValue": "true" + }, { "name": "spring.jpa.defer-datasource-initialization", "type": "java.lang.Boolean", diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index a223cfa1a66e..20ff59a95486 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -194,6 +194,30 @@ void loadWhenHasIgnoreUnknownFieldsFalseAndUnknownFieldsShouldFail() { .withCauseInstanceOf(BindException.class); } + @Test + void loadWhenIgnoreUnresolvablePlaceholdersSetsToFalseShouldFail() { + assertThatExceptionOfType(ConfigurationPropertiesBindException.class) + .isThrownBy(() -> load(BasicConfiguration.class, "name=${FOO}", + "spring.configurationproperties.ignore-unresolvable-placeholders=false")) + .withCauseInstanceOf(BindException.class) + .withStackTraceContaining("Could not resolve placeholder 'FOO'"); + } + + @Test + void loadWhenIgnoreUnresolvablePlaceholdersSetsToTrueShouldNotFail() { + load(BasicConfiguration.class, "name=${FOO}", + "spring.configurationproperties.ignore-unresolvable-placeholders=true"); + BasicProperties properties = this.context.getBean(BasicProperties.class); + assertThat(properties.name).isEqualTo("${FOO}"); + } + + @Test + void loadWhenIgnoreUnresolvablePlaceholdersSetsToTrueByDefaultShouldNotFail() { + load(BasicConfiguration.class, "name=${FOO}"); + BasicProperties properties = this.context.getBean(BasicProperties.class); + assertThat(properties.name).isEqualTo("${FOO}"); + } + @Test void givenIgnoreUnknownFieldsFalseAndIgnoreInvalidFieldsTrueWhenThereAreUnknownFieldsThenBindingShouldFail() { removeSystemProperties();