diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc
index ab359e9585..2aa5207aad 100644
--- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc
+++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc
@@ -623,6 +623,9 @@ You can use this prefix with the `spring.config.location` and `spring.config.add
For example, a `spring.config.import` value of `optional:file:./myconfig.properties` allows your application to start, even if the `myconfig.properties` file is missing.
+If you want all locations to be considered optional, you can use the `spring.config.all-locations-optional` property.
+Set the value to `true` using `SpringApplication.setDefaultProperties(...)` or with a system/environment variable.
+
[[boot-features-external-config-files-wildcards]]
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java
index 410b7f2794..14d6e7fbe0 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java
@@ -50,8 +50,8 @@ import org.springframework.util.StringUtils;
* {@link Environment} and adding the initial set of imports.
*
* The initial imports can be influenced via the {@link #LOCATION_PROPERTY},
- * {@value #ADDITIONAL_LOCATION_PROPERTY} and {@value #SPRING_CONFIG_IMPORT} properties.
- * If not explicit properties are set, the {@link #DEFAULT_SEARCH_LOCATIONS} will be used.
+ * {@value #ADDITIONAL_LOCATION_PROPERTY} and {@value #IMPORT_PROPERTY} properties. If not
+ * explicit properties are set, the {@link #DEFAULT_SEARCH_LOCATIONS} will be used.
*
* @author Phillip Webb
* @author Madhura Bhave
@@ -71,7 +71,13 @@ class ConfigDataEnvironment {
/**
* Property used to provide additional locations to import.
*/
- static final String SPRING_CONFIG_IMPORT = "spring.config.import";
+ static final String IMPORT_PROPERTY = "spring.config.import";
+
+ /**
+ * Property used to determine if all locations are optional and
+ * {@code ConfigDataLocationNotFoundExceptions} should be ignored.
+ */
+ static final String ALL_LOCATIONS_OPTIONAL_PROPERTY = "spring.config.all-locations-optional";
/**
* Default search locations used if not {@link #LOCATION_PROPERTY} is found.
@@ -114,19 +120,20 @@ class ConfigDataEnvironment {
ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection additionalProfiles) {
Binder binder = Binder.get(environment);
UseLegacyConfigProcessingException.throwIfRequested(binder);
+ boolean allLocationsOptional = binder.bind(ALL_LOCATIONS_OPTIONAL_PROPERTY, Boolean.class).orElse(false);
this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass());
this.bootstrapRegistry = bootstrapRegistry;
this.environment = environment;
- this.resolvers = createConfigDataLocationResolvers(logFactory, binder, resourceLoader);
+ this.resolvers = createConfigDataLocationResolvers(logFactory, allLocationsOptional, binder, resourceLoader);
this.additionalProfiles = additionalProfiles;
- this.loaders = new ConfigDataLoaders(logFactory);
+ this.loaders = new ConfigDataLoaders(logFactory, allLocationsOptional);
this.contributors = createContributors(binder);
}
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
- Binder binder, ResourceLoader resourceLoader) {
- return new ConfigDataLocationResolvers(logFactory, binder, resourceLoader);
+ boolean allLocationsOptional, Binder binder, ResourceLoader resourceLoader) {
+ return new ConfigDataLocationResolvers(logFactory, allLocationsOptional, binder, resourceLoader);
}
private ConfigDataEnvironmentContributors createContributors(Binder binder) {
@@ -159,7 +166,7 @@ class ConfigDataEnvironment {
private List getInitialImportContributors(Binder binder) {
List initialContributors = new ArrayList<>();
addInitialImportContributors(initialContributors,
- binder.bind(SPRING_CONFIG_IMPORT, String[].class).orElse(EMPTY_LOCATIONS));
+ binder.bind(IMPORT_PROPERTY, String[].class).orElse(EMPTY_LOCATIONS));
addInitialImportContributors(initialContributors,
binder.bind(ADDITIONAL_LOCATION_PROPERTY, String[].class).orElse(EMPTY_LOCATIONS));
addInitialImportContributors(initialContributors,
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessor.java
index 3f7c0eb58a..4bc000564f 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessor.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessor.java
@@ -50,6 +50,12 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces
*/
public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
+ /**
+ * Property used to determine if all locations are optional and
+ * {@code ConfigDataLocationNotFoundExceptions} should be ignored.
+ */
+ public static final String ALL_LOCATIONS_OPTIONAL_PROPERTY = ConfigDataEnvironment.ALL_LOCATIONS_OPTIONAL_PROPERTY;
+
private final DeferredLogFactory logFactory;
private final Log logger;
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java
index 9aa0c07219..935a205e45 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java
@@ -40,25 +40,30 @@ class ConfigDataLoaders {
private final Log logger;
+ private final boolean allLocationsOptional;
+
private final List> loaders;
private final List> locationTypes;
/**
* Create a new {@link ConfigDataLoaders} instance.
+ * @param allLocationsOptional if all locations are considered optional
* @param logFactory the deferred log factory
*/
- ConfigDataLoaders(DeferredLogFactory logFactory) {
- this(logFactory, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null));
+ ConfigDataLoaders(DeferredLogFactory logFactory, boolean allLocationsOptional) {
+ this(logFactory, allLocationsOptional, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null));
}
/**
* Create a new {@link ConfigDataLoaders} instance.
* @param logFactory the deferred log factory
+ * @param allLocationsOptional if all locations are considered optional
* @param names the {@link ConfigDataLoader} class names instantiate
*/
- ConfigDataLoaders(DeferredLogFactory logFactory, List names) {
+ ConfigDataLoaders(DeferredLogFactory logFactory, boolean allLocationsOptional, List names) {
this.logger = logFactory.getLog(getClass());
+ this.allLocationsOptional = allLocationsOptional;
Instantiator> instantiator = new Instantiator<>(ConfigDataLoader.class,
(availableParameters) -> availableParameters.add(Log.class, logFactory::getLog));
this.loaders = instantiator.instantiate(names);
@@ -99,11 +104,11 @@ class ConfigDataLoaders {
return loader.load(context, location);
}
catch (ConfigDataLocationNotFoundException ex) {
- if (!optional) {
- throw ex;
+ if (this.allLocationsOptional || optional) {
+ this.logger.trace(LogMessage.format("Skipping missing resource from optional location %s", location));
+ return null;
}
- this.logger.trace(LogMessage.format("Skipping missing resource from optional location %s", location));
- return null;
+ throw ex;
}
}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java
index bfe2991ef5..34d7b35b5f 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java
@@ -43,29 +43,35 @@ class ConfigDataLocationResolvers {
private final Log logger;
+ private final boolean allLocationsOptional;
+
private final List> resolvers;
/**
* Create a new {@link ConfigDataLocationResolvers} instance.
* @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances
+ * @param allLocationsOptional if all locations are considered optional
* @param binder a binder providing values from the initial {@link Environment}
* @param resourceLoader {@link ResourceLoader} to load resource locations
*/
- ConfigDataLocationResolvers(DeferredLogFactory logFactory, Binder binder, ResourceLoader resourceLoader) {
- this(logFactory, binder, resourceLoader,
+ ConfigDataLocationResolvers(DeferredLogFactory logFactory, boolean allLocationsOptional, Binder binder,
+ ResourceLoader resourceLoader) {
+ this(logFactory, allLocationsOptional, binder, resourceLoader,
SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, null));
}
/**
* Create a new {@link ConfigDataLocationResolvers} instance.
* @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances
+ * @param allLocationsOptional if all locations are considered optional
* @param binder {@link Binder} providing values from the initial {@link Environment}
* @param resourceLoader {@link ResourceLoader} to load resource locations
* @param names the {@link ConfigDataLocationResolver} class names
*/
- ConfigDataLocationResolvers(DeferredLogFactory logFactory, Binder binder, ResourceLoader resourceLoader,
- List names) {
+ ConfigDataLocationResolvers(DeferredLogFactory logFactory, boolean allLocationsOptional, Binder binder,
+ ResourceLoader resourceLoader, List names) {
this.logger = logFactory.getLog(getClass());
+ this.allLocationsOptional = allLocationsOptional;
Instantiator> instantiator = new Instantiator<>(ConfigDataLocationResolver.class,
(availableParameters) -> {
availableParameters.add(Log.class, logFactory::getLog);
@@ -146,11 +152,11 @@ class ConfigDataLocationResolvers {
return resolved;
}
catch (ConfigDataLocationNotFoundException ex) {
- if (!optional) {
- throw ex;
+ if (this.allLocationsOptional || optional) {
+ this.logger.trace(LogMessage.format("Skipping missing resource from optional location %s", location));
+ return Collections.emptyList();
}
- this.logger.trace(LogMessage.format("Skipping missing resource from optional location %s", location));
- return Collections.emptyList();
+ throw ex;
}
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java
index 012fff9912..2c8c117084 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java
@@ -80,8 +80,9 @@ class ConfigDataEnvironmentContributorsTests {
void setup() {
this.environment = new MockEnvironment();
this.binder = Binder.get(this.environment);
- ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder, null);
- ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory);
+ ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, true, this.binder,
+ null);
+ ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, false);
this.importer = new ConfigDataImporter(resolvers, loaders);
this.activationContext = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, null);
}
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java
index 53e2b06cb5..431eb3b102 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java
@@ -549,6 +549,12 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
() -> this.application.run("--spring.config.location=classpath:missing-appplication.properties"));
}
+ @Test
+ void runWhenHasNonOptionalImportAndFailFalsePropertyIgnoresException() {
+ this.application.run("--spring.config.fail-on-location-not-found=false",
+ "--spring.config.location=classpath:missing-appplication.properties");
+ }
+
@Test
void runWhenHasIncludedProfilesActivatesProfiles() {
ConfigurableApplicationContext context = this.application
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java
index a4988661d3..9b6bf67a1e 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java
@@ -214,9 +214,10 @@ class ConfigDataEnvironmentTests {
@Override
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
- Binder binder, ResourceLoader resourceLoader) {
+ boolean failOnConfigDataLocationNotFound, Binder binder, ResourceLoader resourceLoader) {
this.configDataLocationResolversBinder = binder;
- return super.createConfigDataLocationResolvers(logFactory, binder, resourceLoader);
+ return super.createConfigDataLocationResolvers(logFactory, failOnConfigDataLocationNotFound, binder,
+ resourceLoader);
}
Binder getConfigDataLocationResolversBinder() {
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java
index d8311df8a3..08ca86e348 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java
@@ -46,13 +46,13 @@ class ConfigDataLoadersTests {
@Test
void createWhenLoaderHasLogParameterInjectsLog() {
- new ConfigDataLoaders(this.logFactory, Arrays.asList(LoggingConfigDataLoader.class.getName()));
+ new ConfigDataLoaders(this.logFactory, true, Arrays.asList(LoggingConfigDataLoader.class.getName()));
}
@Test
void loadWhenSingleLoaderSupportsLocationReturnsLoadedConfigData() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test");
- ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory,
+ ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, false,
Arrays.asList(TestConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(this.context, location);
assertThat(getLoader(loaded)).isInstanceOf(TestConfigDataLoader.class);
@@ -61,7 +61,7 @@ class ConfigDataLoadersTests {
@Test
void loadWhenMultipleLoadersSupportLocationThrowsException() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test");
- ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory,
+ ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, false,
Arrays.asList(LoggingConfigDataLoader.class.getName(), TestConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location))
.withMessageContaining("Multiple loaders found for location test");
@@ -70,7 +70,7 @@ class ConfigDataLoadersTests {
@Test
void loadWhenNoLoaderSupportsLocationThrowsException() {
TestConfigDataLocation location = new TestConfigDataLocation("test");
- ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory,
+ ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, false,
Arrays.asList(NonLoadableConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location))
.withMessage("No loader found for location 'test'");
@@ -79,7 +79,7 @@ class ConfigDataLoadersTests {
@Test
void loadWhenGenericTypeDoesNotMatchSkipsLoader() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test");
- ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory,
+ ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, false,
Arrays.asList(OtherConfigDataLoader.class.getName(), SpecificConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(this.context, location);
assertThat(getLoader(loaded)).isInstanceOf(SpecificConfigDataLoader.class);
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java
index b3c16b24e6..09c7f42ab9 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java
@@ -63,7 +63,7 @@ class ConfigDataLocationResolversTests {
@Test
void createWhenInjectingBinderCreatesResolver() {
- ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder,
+ ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader, Collections.singletonList(TestBoundResolver.class.getName()));
assertThat(resolvers.getResolvers()).hasSize(1);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestBoundResolver.class);
@@ -72,7 +72,7 @@ class ConfigDataLocationResolversTests {
@Test
void createWhenNotInjectingBinderCreatesResolver() {
- ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder,
+ ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader, Collections.singletonList(TestResolver.class.getName()));
assertThat(resolvers.getResolvers()).hasSize(1);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestResolver.class);
@@ -81,8 +81,8 @@ class ConfigDataLocationResolversTests {
@Test
void createWhenNameIsNotConfigDataLocationResolverThrowsException() {
assertThatIllegalArgumentException()
- .isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, this.binder, this.resourceLoader,
- Collections.singletonList(InputStream.class.getName())))
+ .isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
+ this.resourceLoader, Collections.singletonList(InputStream.class.getName())))
.withMessageContaining("Unable to instantiate").havingCause().withMessageContaining("not assignable");
}
@@ -92,7 +92,7 @@ class ConfigDataLocationResolversTests {
names.add(TestResolver.class.getName());
names.add(LowestTestResolver.class.getName());
names.add(HighestTestResolver.class.getName());
- ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder,
+ ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader, names);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(HighestTestResolver.class);
assertThat(resolvers.getResolvers().get(1)).isExactlyInstanceOf(TestResolver.class);
@@ -101,7 +101,7 @@ class ConfigDataLocationResolversTests {
@Test
void resolveAllResolvesUsingFirstSupportedResolver() {
- ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder,
+ ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
List resolved = resolvers.resolveAll(this.context,
@@ -115,7 +115,7 @@ class ConfigDataLocationResolversTests {
@Test
void resolveAllWhenProfileMergesResolvedLocations() {
- ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder,
+ ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
List resolved = resolvers.resolveAll(this.context,
@@ -133,7 +133,7 @@ class ConfigDataLocationResolversTests {
@Test
void resolveWhenNoResolverThrowsException() {
- ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder,
+ ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, false, this.binder,
this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
assertThatExceptionOfType(UnsupportedConfigDataLocationException.class)