docs
This documentation has been enhanced for AI coding agents with comprehensive examples, complete API signatures, thread safety notes, error handling patterns, and production-ready usage patterns.
| Component | Purpose | Thread Safety | Key Features |
|---|---|---|---|
ConfigData | Container for loaded configuration | Thread-safe (immutable) | Property sources, options, profile-specific data |
ConfigDataLocation | User-specified location | Thread-safe (immutable) | Wildcards, optional locations, profile-specific |
ConfigDataResource | Resolved loadable resource | Thread-safe (immutable) | Origin tracking, profile-specific handling |
ConfigDataLocationResolver | Resolve locations to resources | Thread-safe | Custom location resolution, profile support |
ConfigDataLoader | Load data from resources | Thread-safe | YAML, properties, custom formats |
ConfigDataEnvironmentPostProcessor | Orchestrates loading | Thread-safe | Entry point for ConfigData system |
Profiles | Manages active/default profiles | Thread-safe (immutable) | Profile checking, iteration, active/default separation |
ConfigDataLoaderContext | Context for loaders | Thread-safe | Bootstrap context access |
ConfigDataLocationResolverContext | Context for resolvers | Thread-safe | Binder, parent resource, bootstrap context |
ConfigDataEnvironmentUpdateListener | Tracks environment updates | Thread-safe | Property source tracking, profile change notification |
ConfigDataNotFoundAction | Action on missing config | Thread-safe (enum) | FAIL or IGNORE missing configuration |
| Option | Purpose | Thread Safety | Behavior |
|---|---|---|---|
PROFILE_SPECIFIC | Profile-specific config | Thread-safe (enum) | Only loaded for active profiles |
IGNORE_IMPORTS | Skip import processing | Thread-safe (enum) | Don't process spring.config.import |
IGNORE_PROFILES | Skip profile activation | Thread-safe (enum) | Don't activate profiles from this source |
| Resolver | Supported Locations | Thread Safety | Features |
|---|---|---|---|
StandardConfigDataLocationResolver | classpath:, file:, optional: | Thread-safe | Standard file system and classpath loading |
ConfigTreeConfigDataLocationResolver | configtree: | Thread-safe | Kubernetes ConfigMap/Secret directories |
| Loader | Supported Resources | Thread Safety | Formats |
|---|---|---|---|
StandardConfigDataLoader | File-based resources | Thread-safe | .properties, .xml, .yaml, .yml |
ConfigTreeConfigDataLoader | Config tree resources | Thread-safe | Directory-based key-value pairs |
| Exception | Purpose | When Thrown | Recovery |
|---|---|---|---|
ConfigDataNotFoundException | Resource not found | Loading missing required config | Make location optional: optional:classpath:/config.yml |
ConfigDataResourceNotFoundException | Specific resource missing | Resolver can't find resource | Check file path, make optional |
ConfigDataNotFoundAction | Action on not found | Configuration | FAIL (default) or IGNORE |
This document covers Spring Boot's configuration data loading mechanism, which provides a flexible and extensible way to load configuration from various sources during application startup.
Spring Boot's configuration data loading system is built around several key abstractions:
ConfigData represents configuration data that has been loaded and may contribute property sources to Spring's Environment.
package org.springframework.boot.context.config;
import java.util.Collection;
import java.util.List;
import org.springframework.core.env.PropertySource;
/**
* Configuration data that has been loaded from a ConfigDataResource and may
* ultimately contribute PropertySource instances to Spring's Environment.
*
* @since 2.4.0
*/
public final class ConfigData {
/**
* A ConfigData instance that contains no data.
*/
public static final ConfigData EMPTY = new ConfigData(Collections.emptySet());
/**
* Create a new ConfigData instance with the same options applied to each source.
*
* @param propertySources the config data property sources in ascending priority order
* @param options the config data options applied to each source
*/
public ConfigData(Collection<? extends PropertySource<?>> propertySources,
Option... options);
/**
* Create a new ConfigData instance with specific property source options.
*
* @param propertySources the config data property sources in ascending priority order
* @param propertySourceOptions the property source options
* @since 2.4.5
*/
public ConfigData(Collection<? extends PropertySource<?>> propertySources,
PropertySourceOptions propertySourceOptions);
/**
* Return the configuration data property sources in ascending priority order.
* If the same key is contained in more than one of the sources, then the later
* source will win.
*
* @return the config data property sources
*/
public List<PropertySource<?>> getPropertySources();
/**
* Return the config data options that apply to the given source.
*
* @param propertySource the property source to check
* @return the options that apply
* @since 2.4.5
*/
public Options getOptions(PropertySource<?> propertySource);
/**
* Option flags that can be applied.
*/
public enum Option {
/**
* Ignore all imports properties from the source.
*/
IGNORE_IMPORTS,
/**
* Ignore all profile activation and include properties.
* @since 2.4.3
*/
IGNORE_PROFILES,
/**
* Indicates that the source is "profile specific" and should be included
* after profile specific sibling imports.
* @since 2.4.5
*/
PROFILE_SPECIFIC
}
/**
* A set of Option flags.
* @since 2.4.5
*/
public static final class Options {
/**
* No options.
*/
public static final Options NONE = new Options(Collections.emptySet());
/**
* Returns if the given option is contained in this set.
*
* @param option the option to check
* @return true if the option is present
*/
public boolean contains(Option option);
/**
* Create a new Options instance that contains the options in this set
* excluding the given option.
*
* @param option the option to exclude
* @return a new Options instance
*/
public Options without(Option option);
/**
* Create a new Options instance that contains the options in this set
* including the given option.
*
* @param option the option to include
* @return a new Options instance
*/
public Options with(Option option);
/**
* Create a new instance with the given Option values.
*
* @param options the options to include
* @return a new Options instance
*/
public static Options of(Option... options);
}
/**
* Strategy interface used to supply Options for a given PropertySource.
* @since 2.4.5
*/
@FunctionalInterface
public interface PropertySourceOptions {
/**
* PropertySourceOptions instance that always returns Options.NONE.
* @since 2.4.6
*/
PropertySourceOptions ALWAYS_NONE = /* implementation */;
/**
* Return the options that should apply for the given property source.
*
* @param propertySource the property source
* @return the options to apply
*/
Options get(PropertySource<?> propertySource);
/**
* Create a new PropertySourceOptions instance that always returns the
* same options regardless of the property source.
*
* @param options the options to return
* @return a new PropertySourceOptions instance
*/
static PropertySourceOptions always(Option... options);
/**
* Create a new PropertySourceOptions instance that always returns the
* same options regardless of the property source.
*
* @param options the options to return
* @return a new PropertySourceOptions instance
*/
static PropertySourceOptions always(Options options);
}
}ConfigDataLocation represents a user-specified location that can be resolved to one or more resources.
package org.springframework.boot.context.config;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginProvider;
/**
* A user specified location that can be resolved to one or more config data resources.
* A ConfigDataLocation is a simple wrapper around a String value. The exact format of
* the value will depend on the underlying technology, but is usually a URL-like syntax
* consisting of a prefix and path. For example, "crypt:somehost/somepath".
*
* Locations can be mandatory or optional. Optional locations are prefixed with "optional:".
*
* @since 2.4.0
*/
public final class ConfigDataLocation implements OriginProvider {
/**
* Prefix used to indicate that a ConfigDataResource is optional.
*/
public static final String OPTIONAL_PREFIX = "optional:";
/**
* Return if the location is optional and should ignore ConfigDataNotFoundException.
*
* @return if the location is optional
*/
public boolean isOptional();
/**
* Return the value of the location (always excluding any user specified
* "optional:" prefix).
*
* @return the location value
*/
public String getValue();
/**
* Return if getValue() has the specified prefix.
*
* @param prefix the prefix to check
* @return if the value has the prefix
*/
public boolean hasPrefix(String prefix);
/**
* Return getValue() with the specified prefix removed. If the location does
* not have the given prefix then the getValue() is returned unchanged.
*
* @param prefix the prefix to check
* @return the value with the prefix removed
*/
public String getNonPrefixedValue(String prefix);
@Override
public Origin getOrigin();
/**
* Return an array of ConfigDataLocation elements built by splitting this
* ConfigDataLocation around a delimiter of ";".
*
* @return the split locations
* @since 2.4.7
*/
public ConfigDataLocation[] split();
/**
* Return an array of ConfigDataLocation elements built by splitting this
* ConfigDataLocation around the specified delimiter.
*
* @param delimiter the delimiter to split on
* @return the split locations
* @since 2.4.7
*/
public ConfigDataLocation[] split(String delimiter);
/**
* Factory method to create a new ConfigDataLocation from a string.
*
* @param location the location string
* @return the ConfigDataLocation (which may be empty)
*/
public static ConfigDataLocation of(String location);
}ConfigDataResource represents a single resource from which configuration data can be loaded.
package org.springframework.boot.context.config;
/**
* A single resource from which ConfigData can be loaded. Implementations must
* implement a valid equals(), hashCode(), and toString() methods.
*
* @since 2.4.0
*/
public abstract class ConfigDataResource {
/**
* Create a new non-optional ConfigDataResource instance.
*/
public ConfigDataResource();
/**
* Create a new ConfigDataResource instance.
*
* @param optional if the resource is optional
* @since 2.4.6
*/
protected ConfigDataResource(boolean optional);
}ConfigDataLocationResolver resolves locations into resources.
package org.springframework.boot.context.config;
import java.util.List;
import org.springframework.boot.bootstrap.BootstrapContext;
import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.boot.bootstrap.ConfigurableBootstrapContext;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.io.ResourceLoader;
/**
* Strategy interface used to resolve ConfigDataLocation locations into one or more
* ConfigDataResource resources. Implementations should be added as spring.factories
* entries.
*
* The following constructor parameter types are supported:
* - DeferredLogFactory - if the resolver needs deferred logging
* - Binder - if the resolver needs to obtain values from the initial Environment
* - ResourceLoader - if the resolver needs a resource loader
* - ConfigurableBootstrapContext - A bootstrap context that can be used to store
* objects that may be expensive to create, or need to be shared (BootstrapContext
* or BootstrapRegistry may also be used)
*
* Resolvers may implement Ordered or use the @Order annotation. The first resolver
* that supports the given location will be used.
*
* @param <R> the resource type
* @since 2.4.0
*/
public interface ConfigDataLocationResolver<R extends ConfigDataResource> {
/**
* Returns if the specified location address can be resolved by this resolver.
*
* @param context the location resolver context
* @param location the location to check
* @return if the location is supported by this resolver
*/
boolean isResolvable(ConfigDataLocationResolverContext context,
ConfigDataLocation location);
/**
* Resolve a ConfigDataLocation into one or more ConfigDataResource instances.
*
* @param context the location resolver context
* @param location the location that should be resolved
* @return a list of ConfigDataResource resources in ascending priority order
* @throws ConfigDataLocationNotFoundException on a non-optional location that
* cannot be found
* @throws ConfigDataResourceNotFoundException if a resolved resource cannot be found
*/
List<R> resolve(ConfigDataLocationResolverContext context,
ConfigDataLocation location)
throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException;
/**
* Resolve a ConfigDataLocation into one or more ConfigDataResource instances
* based on available profiles. This method is called once profiles have been
* deduced from the contributed values. By default this method returns an empty list.
*
* @param context the location resolver context
* @param location the location that should be resolved
* @param profiles profile information
* @return a list of resolved locations in ascending priority order
* @throws ConfigDataLocationNotFoundException on a non-optional location that
* cannot be found
*/
default List<R> resolveProfileSpecific(ConfigDataLocationResolverContext context,
ConfigDataLocation location,
Profiles profiles)
throws ConfigDataLocationNotFoundException;
}ConfigDataLoader loads configuration data from resources.
package org.springframework.boot.context.config;
import java.io.IOException;
import org.springframework.boot.bootstrap.BootstrapContext;
import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.boot.bootstrap.ConfigurableBootstrapContext;
import org.springframework.boot.logging.DeferredLogFactory;
/**
* Strategy class that can be used to load ConfigData for a given ConfigDataResource.
* Implementations should be added as spring.factories entries.
*
* The following constructor parameter types are supported:
* - DeferredLogFactory - if the loader needs deferred logging
* - ConfigurableBootstrapContext - A bootstrap context that can be used to store
* objects that may be expensive to create, or need to be shared (BootstrapContext
* or BootstrapRegistry may also be used)
*
* Multiple loaders cannot claim the same resource.
*
* @param <R> the resource type
* @since 2.4.0
*/
public interface ConfigDataLoader<R extends ConfigDataResource> {
/**
* Returns if the specified resource can be loaded by this instance.
*
* @param context the loader context
* @param resource the resource to check
* @return if the resource is supported by this loader
*/
default boolean isLoadable(ConfigDataLoaderContext context, R resource);
/**
* Load ConfigData for the given resource.
*
* @param context the loader context
* @param resource the resource to load
* @return the loaded config data or null if the location should be skipped
* @throws IOException on IO error
* @throws ConfigDataResourceNotFoundException if the resource cannot be found
*/
ConfigData load(ConfigDataLoaderContext context, R resource)
throws IOException, ConfigDataResourceNotFoundException;
}ConfigDataEnvironmentPostProcessor orchestrates the configuration data loading process.
package org.springframework.boot.context.config;
import java.util.Collection;
import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.bootstrap.ConfigurableBootstrapContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.ResourceLoader;
/**
* EnvironmentPostProcessor that loads and applies ConfigData to Spring's Environment.
*
* @since 2.4.0
*/
public class ConfigDataEnvironmentPostProcessor
implements EnvironmentPostProcessor, Ordered {
/**
* The default order for the processor.
*/
public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
/**
* Property used to determine what action to take when a
* ConfigDataLocationNotFoundException is thrown.
*
* @see ConfigDataNotFoundAction
*/
public static final String ON_LOCATION_NOT_FOUND_PROPERTY =
"spring.config.on-not-found";
/**
* Constructor with deferred logging factory and bootstrap context.
*
* @param logFactory the deferred log factory
* @param bootstrapContext the bootstrap context
*/
public ConfigDataEnvironmentPostProcessor(DeferredLogFactory logFactory,
ConfigurableBootstrapContext bootstrapContext);
@Override
public int getOrder();
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application);
/**
* Apply ConfigData post-processing to an existing Environment. This method can
* be useful when working with an Environment that has been created directly and
* not necessarily as part of a SpringApplication.
*
* @param environment the environment to apply ConfigData to
*/
public static void applyTo(ConfigurableEnvironment environment);
/**
* Apply ConfigData post-processing to an existing Environment. This method can
* be useful when working with an Environment that has been created directly and
* not necessarily as part of a SpringApplication.
*
* @param environment the environment to apply ConfigData to
* @param resourceLoader the resource loader to use
* @param bootstrapContext the bootstrap context to use or null to use a throw-away
* context
* @param additionalProfiles any additional profiles that should be applied
*/
public static void applyTo(ConfigurableEnvironment environment,
ResourceLoader resourceLoader,
ConfigurableBootstrapContext bootstrapContext,
String... additionalProfiles);
/**
* Apply ConfigData post-processing to an existing Environment. This method can
* be useful when working with an Environment that has been created directly and
* not necessarily as part of a SpringApplication.
*
* @param environment the environment to apply ConfigData to
* @param resourceLoader the resource loader to use
* @param bootstrapContext the bootstrap context to use or null to use a throw-away
* context
* @param additionalProfiles any additional profiles that should be applied
*/
public static void applyTo(ConfigurableEnvironment environment,
ResourceLoader resourceLoader,
ConfigurableBootstrapContext bootstrapContext,
Collection<String> additionalProfiles);
/**
* Apply ConfigData post-processing to an existing Environment with an update listener.
*
* @param environment the environment to apply ConfigData to
* @param resourceLoader the resource loader to use
* @param bootstrapContext the bootstrap context to use or null to use a throw-away
* context
* @param additionalProfiles any additional profiles that should be applied
* @param environmentUpdateListener optional ConfigDataEnvironmentUpdateListener
* that can be used to track Environment updates
*/
public static void applyTo(ConfigurableEnvironment environment,
ResourceLoader resourceLoader,
ConfigurableBootstrapContext bootstrapContext,
Collection<String> additionalProfiles,
ConfigDataEnvironmentUpdateListener environmentUpdateListener);
}Profiles provides access to environment profiles that have been set directly or will be set based on configuration data.
package org.springframework.boot.context.config;
import java.util.Iterator;
import java.util.List;
/**
* Provides access to environment profiles that have either been set directly on the
* Environment or will be set based on configuration data property values.
*
* @since 2.4.0
*/
public class Profiles implements Iterable<String> {
/**
* Name of property to set to specify additionally included active profiles.
*/
public static final String INCLUDE_PROFILES_PROPERTY_NAME = "spring.profiles.include";
/**
* Return an iterator for all accepted profiles.
*
* @return an iterator over accepted profiles
*/
@Override
public Iterator<String> iterator();
/**
* Return the active profiles.
*
* @return the active profiles (never null)
*/
public List<String> getActive();
/**
* Return the default profiles.
*
* @return the default profiles (never null)
*/
public List<String> getDefault();
/**
* Return the accepted profiles. This is the union of active and default profiles.
*
* @return the accepted profiles (never null)
*/
public List<String> getAccepted();
/**
* Return if the given profile is active (accepted).
*
* @param profile the profile to test
* @return true if the profile is active
*/
public boolean isAccepted(String profile);
}ConfigDataLoaderContext provides context to ConfigDataLoader methods, mainly for accessing the bootstrap context.
package org.springframework.boot.context.config;
import org.springframework.boot.bootstrap.ConfigurableBootstrapContext;
/**
* Context provided to ConfigDataLoader methods.
*
* @since 2.4.0
*/
public interface ConfigDataLoaderContext {
/**
* Provides access to the ConfigurableBootstrapContext shared across all
* EnvironmentPostProcessors.
*
* @return the bootstrap context (never null)
*/
ConfigurableBootstrapContext getBootstrapContext();
}ConfigDataLocationResolverContext provides context to ConfigDataLocationResolver methods, including access to the binder, parent resource, and bootstrap context.
package org.springframework.boot.context.config;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.bootstrap.ConfigurableBootstrapContext;
import org.springframework.lang.Nullable;
/**
* Context provided to ConfigDataLocationResolver methods.
*
* @since 2.4.0
*/
public interface ConfigDataLocationResolverContext {
/**
* Provides access to a binder that can be used to obtain previously contributed
* values.
*
* @return a binder instance (never null)
*/
Binder getBinder();
/**
* Provides access to the parent ConfigDataResource that triggered the resolve
* or null if there is no available parent.
*
* @return the parent resource or null
*/
@Nullable
ConfigDataResource getParent();
/**
* Provides access to the ConfigurableBootstrapContext shared across all
* EnvironmentPostProcessors.
*
* @return the bootstrap context (never null)
*/
ConfigurableBootstrapContext getBootstrapContext();
}ConfigDataEnvironmentUpdateListener is an event listener interface for tracking Environment updates triggered by ConfigDataEnvironmentPostProcessor.
package org.springframework.boot.context.config;
import java.util.EventListener;
import org.springframework.core.env.PropertySource;
import org.springframework.lang.Nullable;
/**
* EventListener to listen to Environment updates triggered by the
* ConfigDataEnvironmentPostProcessor.
*
* @since 2.4.2
*/
public interface ConfigDataEnvironmentUpdateListener extends EventListener {
/**
* A ConfigDataEnvironmentUpdateListener that does nothing.
* Useful as a null-object implementation.
*/
ConfigDataEnvironmentUpdateListener NONE = new ConfigDataEnvironmentUpdateListener() {};
/**
* Called when a new PropertySource is added to the Environment.
*
* @param propertySource the PropertySource that was added
* @param location the original ConfigDataLocation of the source (may be null)
* @param resource the ConfigDataResource of the source (may be null)
*/
default void onPropertySourceAdded(PropertySource<?> propertySource,
@Nullable ConfigDataLocation location,
@Nullable ConfigDataResource resource) {
}
/**
* Called when Environment profiles are set.
*
* @param profiles the profiles being set
*/
default void onSetProfiles(Profiles profiles) {
}
/**
* Called when config data options are obtained for a particular property source.
* Allows modification of options before they are used.
*
* @param configData the config data
* @param propertySource the property source
* @param options the options as provided by ConfigData.getOptions(PropertySource)
* @return the actual options that should be used (return the input options if no changes needed)
* @since 3.5.1
*/
default ConfigData.Options onConfigDataOptions(ConfigData configData,
PropertySource<?> propertySource,
ConfigData.Options options) {
return options;
}
}ConfigDataNotFoundAction determines what action to take when an uncaught ConfigDataNotFoundException is thrown.
package org.springframework.boot.context.config;
import org.apache.commons.logging.Log;
/**
* Action to take when an uncaught ConfigDataNotFoundException is thrown.
*
* @since 2.4.0
*/
public enum ConfigDataNotFoundAction {
/**
* Throw the exception to fail startup.
* This is the default behavior.
*/
FAIL,
/**
* Ignore the exception and continue processing the remaining locations.
* Use this to make configuration locations optional without using the
* "optional:" prefix.
*/
IGNORE
}Usage:
Set the action via the spring.config.on-not-found property (referenced by ConfigDataEnvironmentPostProcessor.ON_LOCATION_NOT_FOUND_PROPERTY):
# Continue processing even if required config is not found
spring.config.on-not-found=ignoreOr in YAML:
spring:
config:
on-not-found: ignoreStandardConfigDataLoader loads standard configuration files using property source loaders.
package org.springframework.boot.context.config;
import java.io.IOException;
import org.springframework.core.io.Resource;
/**
* ConfigDataLoader for Resource backed locations.
*
* @since 2.4.0
*/
public class StandardConfigDataLoader
implements ConfigDataLoader<StandardConfigDataResource> {
/**
* Load configuration data from the given resource.
*
* @param context the loader context
* @param resource the resource to load
* @return the loaded ConfigData
* @throws IOException on IO error
* @throws ConfigDataNotFoundException if the resource cannot be found
*/
@Override
public ConfigData load(ConfigDataLoaderContext context,
StandardConfigDataResource resource)
throws IOException, ConfigDataNotFoundException;
}ConfigTreeConfigDataLoader loads configuration from config tree structures (e.g., Kubernetes ConfigMaps).
package org.springframework.boot.context.config;
import java.io.IOException;
import java.nio.file.Path;
/**
* ConfigDataLoader for config tree locations. Config trees are directories where
* each file represents a property, useful for Kubernetes ConfigMaps and Secrets.
*
* @since 2.4.0
*/
public class ConfigTreeConfigDataLoader
implements ConfigDataLoader<ConfigTreeConfigDataResource> {
/**
* Load configuration data from the given config tree resource.
*
* @param context the loader context
* @param resource the config tree resource to load
* @return the loaded ConfigData
* @throws IOException on IO error
* @throws ConfigDataResourceNotFoundException if the resource cannot be found
*/
@Override
public ConfigData load(ConfigDataLoaderContext context,
ConfigTreeConfigDataResource resource)
throws IOException, ConfigDataResourceNotFoundException;
}Configuration data loading defines several exception types for error handling.
package org.springframework.boot.context.config;
/**
* Abstract base class for configuration data exceptions.
* @since 2.4.0
*/
public abstract class ConfigDataException extends RuntimeException {
protected ConfigDataException(String message, Throwable cause);
}package org.springframework.boot.context.config;
/**
* Exception thrown when ConfigData cannot be found.
* @since 2.4.0
*/
public abstract class ConfigDataNotFoundException extends ConfigDataException
implements OriginProvider {
public abstract String getReferenceDescription();
}package org.springframework.boot.context.config;
/**
* Exception thrown when a ConfigDataLocation cannot be found.
* @since 2.4.0
*/
public class ConfigDataLocationNotFoundException extends ConfigDataNotFoundException {
public ConfigDataLocationNotFoundException(ConfigDataLocation location);
public ConfigDataLocationNotFoundException(ConfigDataLocation location, Throwable cause);
public ConfigDataLocation getLocation();
}package org.springframework.boot.context.config;
/**
* Exception thrown when a ConfigDataResource cannot be found.
* @since 2.4.0
*/
public class ConfigDataResourceNotFoundException extends ConfigDataNotFoundException {
public ConfigDataResourceNotFoundException(ConfigDataResource resource);
public ConfigDataResourceNotFoundException(ConfigDataResource resource, Throwable cause);
public ConfigDataResource getResource();
}package org.springframework.boot.context.config;
/**
* Exception thrown when a ConfigDataLocation is not supported.
* @since 2.4.0
*/
public class UnsupportedConfigDataLocationException extends ConfigDataException {
public UnsupportedConfigDataLocationException(ConfigDataLocation location);
}package org.springframework.boot.context.config;
/**
* Exception thrown when an attempt is made to access inactive config data.
* @since 2.4.5
*/
public class InactiveConfigDataAccessException extends ConfigDataException {
public InactiveConfigDataAccessException(ConfigDataResource resource);
public ConfigDataResource getResource();
}package org.springframework.boot.context.config;
/**
* Exception thrown when a config data property is invalid.
* @since 2.4.0
*/
public class InvalidConfigDataPropertyException extends ConfigDataException {
public InvalidConfigDataPropertyException(ConfigDataResource resource,
String propertyName,
Object value,
String reason);
public ConfigDataResource getResource();
public String getPropertyName();
}Property source loaders are the underlying mechanism used by StandardConfigDataLoader to load configuration files.
package org.springframework.boot.env;
import java.io.IOException;
import java.util.List;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
/**
* Strategy interface located through SpringFactoriesLoader and used to load a
* PropertySource.
*
* @since 1.0.0
*/
public interface PropertySourceLoader {
/**
* Returns the file extensions that the loader supports (excluding the '.').
*
* @return the file extensions
*/
String[] getFileExtensions();
/**
* Load the resource into one or more property sources. Implementations may either
* return a list containing a single source, or in the case of a multi-document
* format such as yaml a source for each document in the resource.
*
* @param name the root name of the property source. If multiple documents are loaded
* an additional suffix should be added to the name for each source loaded.
* @param resource the resource to load
* @return a list of property sources
* @throws IOException if the source cannot be loaded
*/
List<PropertySource<?>> load(String name, Resource resource) throws IOException;
}package org.springframework.boot.env;
import java.io.IOException;
import java.util.List;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
/**
* Strategy to load '.yml' (or '.yaml') files into a PropertySource.
*
* @since 1.0.0
*/
public class YamlPropertySourceLoader implements PropertySourceLoader {
/**
* Returns the supported file extensions: "yml" and "yaml".
*
* @return the file extensions
*/
@Override
public String[] getFileExtensions();
/**
* Load YAML content from the resource into property sources. For multi-document
* YAML files, returns one property source per document.
*
* @param name the root name of the property source
* @param resource the YAML resource to load
* @return a list of property sources, one per YAML document
* @throws IOException if the resource cannot be loaded
* @throws IllegalStateException if SnakeYAML is not on the classpath
*/
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException;
}package org.springframework.boot.env;
import java.io.IOException;
import java.util.List;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
/**
* Strategy to load '.properties' files into a PropertySource. Also supports
* loading XML properties files.
*
* @since 1.0.0
*/
public class PropertiesPropertySourceLoader implements PropertySourceLoader {
/**
* Returns the supported file extensions: "properties" and "xml".
*
* @return the file extensions
*/
@Override
public String[] getFileExtensions();
/**
* Load properties content from the resource into property sources. Supports both
* standard .properties files and XML properties files.
*
* @param name the root name of the property source
* @param resource the properties resource to load
* @return a list containing the loaded property source
* @throws IOException if the resource cannot be loaded
*/
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException;
}Here's how to implement a custom loader for encrypted configuration files:
package com.example.config;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import org.springframework.boot.context.config.ConfigData;
import org.springframework.boot.context.config.ConfigDataLoader;
import org.springframework.boot.context.config.ConfigDataLoaderContext;
import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.util.StreamUtils;
/**
* Custom ConfigDataLoader that loads encrypted configuration files.
* Register in META-INF/spring.factories:
* org.springframework.boot.context.config.ConfigDataLoader=\
* com.example.config.EncryptedConfigDataLoader
*/
public class EncryptedConfigDataLoader
implements ConfigDataLoader<EncryptedConfigDataResource> {
private final DecryptionService decryptionService;
/**
* Constructor accepting optional dependencies.
* Spring Boot will inject available constructor parameters.
*/
public EncryptedConfigDataLoader() {
this.decryptionService = new DecryptionService();
}
@Override
public boolean isLoadable(ConfigDataLoaderContext context,
EncryptedConfigDataResource resource) {
// Check if the encrypted file exists and is readable
return resource.getPath().toFile().canRead();
}
@Override
public ConfigData load(ConfigDataLoaderContext context,
EncryptedConfigDataResource resource)
throws IOException, ConfigDataResourceNotFoundException {
// Read encrypted content
byte[] encryptedBytes = Files.readAllBytes(resource.getPath());
// Decrypt the content
String decryptedContent = decryptionService.decrypt(encryptedBytes);
// Parse the decrypted properties
Properties properties = new Properties();
properties.load(new StringReader(decryptedContent));
// Convert to Map for PropertySource
Map<String, Object> map = new HashMap<>();
properties.forEach((key, value) -> map.put(key.toString(), value));
// Create PropertySource with origin tracking
String name = "Encrypted config from '" + resource.getPath() + "'";
PropertySource<?> propertySource = new OriginTrackedMapPropertySource(
name, map, true);
// Return ConfigData with the loaded property source
return new ConfigData(Collections.singletonList(propertySource));
}
}Here's how to implement a custom resolver for encrypted configuration locations:
package com.example.config;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.config.ConfigDataLocation;
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
import org.springframework.boot.context.config.ConfigDataLocationResolver;
import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
import org.springframework.boot.context.config.Profiles;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.Ordered;
/**
* Custom ConfigDataLocationResolver that resolves "encrypted:" prefixed locations.
* Register in META-INF/spring.factories:
* org.springframework.boot.context.config.ConfigDataLocationResolver=\
* com.example.config.EncryptedConfigDataLocationResolver
*/
public class EncryptedConfigDataLocationResolver
implements ConfigDataLocationResolver<EncryptedConfigDataResource>, Ordered {
private static final String PREFIX = "encrypted:";
private final Log log;
/**
* Constructor with deferred logging support.
*
* @param logFactory the deferred log factory
*/
public EncryptedConfigDataLocationResolver(DeferredLogFactory logFactory) {
this.log = logFactory.getLog(getClass());
}
@Override
public int getOrder() {
// Run before standard resolvers
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public boolean isResolvable(ConfigDataLocationResolverContext context,
ConfigDataLocation location) {
// Check if location starts with "encrypted:" prefix
return location.hasPrefix(PREFIX);
}
@Override
public List<EncryptedConfigDataResource> resolve(
ConfigDataLocationResolverContext context,
ConfigDataLocation location)
throws ConfigDataLocationNotFoundException,
ConfigDataResourceNotFoundException {
// Remove the "encrypted:" prefix
String path = location.getNonPrefixedValue(PREFIX);
this.log.trace("Resolving encrypted config location: " + path);
// Create the resource
Path filePath = Paths.get(path);
EncryptedConfigDataResource resource =
new EncryptedConfigDataResource(filePath, location.isOptional());
// Check if file exists for non-optional locations
if (!location.isOptional() && !filePath.toFile().exists()) {
throw new ConfigDataResourceNotFoundException(resource);
}
return Collections.singletonList(resource);
}
@Override
public List<EncryptedConfigDataResource> resolveProfileSpecific(
ConfigDataLocationResolverContext context,
ConfigDataLocation location,
Profiles profiles)
throws ConfigDataLocationNotFoundException {
// Handle profile-specific encrypted configs
// e.g., encrypted:config-{profile}.enc
String path = location.getNonPrefixedValue(PREFIX);
List<EncryptedConfigDataResource> resources = new ArrayList<>();
for (String profile : profiles.getAccepted()) {
String profilePath = path.replace("{profile}", profile);
Path filePath = Paths.get(profilePath);
if (filePath.toFile().exists()) {
resources.add(new EncryptedConfigDataResource(
filePath, location.isOptional()));
}
}
return resources;
}
}package com.example.config;
import java.nio.file.Path;
import java.util.Objects;
import org.springframework.boot.context.config.ConfigDataResource;
/**
* ConfigDataResource for encrypted configuration files.
*/
public class EncryptedConfigDataResource extends ConfigDataResource {
private final Path path;
/**
* Create a new EncryptedConfigDataResource.
*
* @param path the path to the encrypted file
* @param optional if the resource is optional
*/
public EncryptedConfigDataResource(Path path, boolean optional) {
super(optional);
this.path = path;
}
/**
* Get the path to the encrypted configuration file.
*
* @return the file path
*/
public Path getPath() {
return this.path;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
EncryptedConfigDataResource other = (EncryptedConfigDataResource) obj;
return Objects.equals(this.path, other.path);
}
@Override
public int hashCode() {
return Objects.hash(this.path);
}
@Override
public String toString() {
return "encrypted config [" + this.path + "]";
}
}Here's how to implement a custom property source loader for a proprietary format:
package com.example.config;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
/**
* Custom PropertySourceLoader for loading JSON configuration files.
* Register in META-INF/spring.factories:
* org.springframework.boot.env.PropertySourceLoader=\
* com.example.config.JsonPropertySourceLoader
*/
public class JsonPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
// Specify the file extensions this loader handles
return new String[] { "json" };
}
@Override
public List<PropertySource<?>> load(String name, Resource resource)
throws IOException {
// Read the JSON file
try (InputStream input = resource.getInputStream()) {
// Parse JSON (using Jackson or Gson)
Map<String, Object> properties = parseJson(input);
if (properties.isEmpty()) {
return Collections.emptyList();
}
// Flatten nested JSON into property keys
Map<String, Object> flattened = flattenMap(properties);
// Create PropertySource with origin tracking
PropertySource<?> propertySource = new OriginTrackedMapPropertySource(
name, Collections.unmodifiableMap(flattened), true);
return Collections.singletonList(propertySource);
}
}
/**
* Parse JSON input stream into a Map.
*
* @param input the JSON input stream
* @return the parsed map
* @throws IOException if parsing fails
*/
private Map<String, Object> parseJson(InputStream input) throws IOException {
// Implementation using JSON parser
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(input,
new TypeReference<Map<String, Object>>() {});
}
/**
* Flatten nested JSON map into property keys with dot notation.
* For example: {"server": {"port": 8080}} becomes {"server.port": "8080"}
*
* @param source the nested map
* @return the flattened map
*/
private Map<String, Object> flattenMap(Map<String, Object> source) {
Map<String, Object> result = new HashMap<>();
flattenMap("", source, result);
return result;
}
private void flattenMap(String prefix, Map<String, Object> source,
Map<String, Object> result) {
source.forEach((key, value) -> {
String fullKey = prefix.isEmpty() ? key : prefix + "." + key;
if (value instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) value;
flattenMap(fullKey, map, result);
} else {
result.put(fullKey, value);
}
});
}
}package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Application using custom configuration loaders.
* Configuration locations can be specified in several ways:
*
* 1. Via spring.config.location property:
* --spring.config.location=encrypted:/etc/config/app.enc
*
* 2. Via spring.config.additional-location property:
* --spring.config.additional-location=encrypted:/secure/secrets.enc
*
* 3. Via spring.config.import property:
* spring.config.import=encrypted:/secure/db-config.enc
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}# application.yml
# Import encrypted configuration
spring:
config:
import:
- encrypted:/etc/config/database.enc
- optional:encrypted:/etc/config/optional-settings.enc
- configtree:/etc/config/configmap/
# Standard configuration
server:
port: 8080
# Properties from encrypted files will be merged with thesepackage com.example;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.origin.OriginTrackedMapPropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;
/**
* Example of programmatically loading configuration data.
*/
public class ProgrammaticConfigExample {
/**
* Load configuration data programmatically into an environment.
*/
public void loadConfiguration() {
// Create a new environment
ConfigurableEnvironment environment = new StandardEnvironment();
// Apply ConfigData processing
ConfigDataEnvironmentPostProcessor.applyTo(environment);
// Now the environment contains all loaded configuration
String property = environment.getProperty("my.custom.property");
System.out.println("Loaded property: " + property);
}
/**
* Load configuration with additional profiles.
*/
public void loadConfigurationWithProfiles() {
ConfigurableEnvironment environment = new StandardEnvironment();
// Apply ConfigData processing with profiles
ConfigDataEnvironmentPostProcessor.applyTo(
environment,
null, // ResourceLoader (null uses default)
null, // BootstrapContext (null creates throw-away)
"dev", "local" // Additional profiles
);
String property = environment.getProperty("my.profile.specific.property");
System.out.println("Profile-specific property: " + property);
}
}To register custom loaders and resolvers, add them to META-INF/spring.factories:
# META-INF/spring.factories
# ConfigDataLocationResolver implementations
org.springframework.boot.context.config.ConfigDataLocationResolver=\
com.example.config.EncryptedConfigDataLocationResolver,\
com.example.config.DatabaseConfigDataLocationResolver
# ConfigDataLoader implementations
org.springframework.boot.context.config.ConfigDataLoader=\
com.example.config.EncryptedConfigDataLoader,\
com.example.config.DatabaseConfigDataLoader
# PropertySourceLoader implementations
org.springframework.boot.env.PropertySourceLoader=\
com.example.config.JsonPropertySourceLoader,\
com.example.config.XmlPropertySourceLoaderSpring Boot provides origin tracking to record where configuration properties came from. This is useful for debugging and displaying helpful error messages that indicate the exact source file and line number of problematic configuration.
The Origin interface represents the origin of a configuration item (e.g., a file with line/column numbers).
package org.springframework.boot.origin;
/**
* Interface that uniquely represents the origin of an item.
* @since 2.0.0
*/
public interface Origin {
/**
* Return the parent origin for this instance if there is one.
* The parent origin provides the origin of the item that created this one.
* @return the parent origin or null
* @since 2.4.0
*/
default Origin getParent();
/**
* Find the Origin that an object originated from.
* Checks if the source object is an Origin or OriginProvider
* and also searches exception stacks.
* @param source the source object or null
* @return an Origin or null
*/
static Origin from(Object source);
/**
* Find the parents of the Origin that an object originated from.
* Provides a list of all parents up to root Origin,
* starting with the most immediate parent.
* @param source the source object or null
* @return a list of parents or an empty list
* @since 2.4.0
*/
static List<Origin> parentsFrom(Object source);
}The OriginProvider interface allows objects to provide their origin information.
package org.springframework.boot.origin;
/**
* Interface to provide access to the origin of an item.
* @since 2.0.0
*/
@FunctionalInterface
public interface OriginProvider {
/**
* Return the source origin or null if the origin is not known.
* @return the origin or null
*/
Origin getOrigin();
}TextResourceOrigin represents an origin from a text-based resource with location information.
package org.springframework.boot.origin;
import org.springframework.core.io.Resource;
/**
* Origin from a text-based Resource.
* Provides access to the resource and optionally to a location within it.
* @since 2.0.0
*/
public class TextResourceOrigin implements Origin {
/**
* Create a new TextResourceOrigin for the given resource.
* @param resource the resource
* @param location the location within the resource (may be null)
*/
public TextResourceOrigin(Resource resource, Location location);
/**
* Return the resource where the property originated.
* @return the text resource origin
*/
public Resource getResource();
/**
* Return the location within the resource where the property originated.
* @return the location within the resource (may be null)
*/
public Location getLocation();
/**
* Location within the resource (line and column numbers).
*/
public static final class Location {
/**
* Create a new Location instance.
* @param line the line number (0-indexed)
* @param column the column number (0-indexed)
*/
public Location(int line, int column);
/**
* Return the line number (0-indexed).
* @return the line number
*/
public int getLine();
/**
* Return the column number (0-indexed).
* @return the column number
*/
public int getColumn();
}
}PropertySourceOrigin represents an origin from a Spring PropertySource.
package org.springframework.boot.origin;
import org.springframework.core.env.PropertySource;
/**
* Origin from a PropertySource.
* @since 2.0.0
*/
public class PropertySourceOrigin implements Origin {
/**
* Get an Origin for the given PropertySource and property name.
* @param propertySource the property source
* @param propertyName the property name
* @return an Origin or null
*/
public static Origin get(PropertySource<?> propertySource, String propertyName);
}import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginProvider;
import org.springframework.boot.origin.TextResourceOrigin;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.core.env.PropertySource;
public class OriginTrackingExample {
public void handleBindingError(BindException ex) {
// Get origin from exception
Origin origin = Origin.from(ex);
if (origin instanceof TextResourceOrigin textOrigin) {
Resource resource = textOrigin.getResource();
TextResourceOrigin.Location location = textOrigin.getLocation();
if (location != null) {
System.err.printf("Error in %s at line %d, column %d%n",
resource.getDescription(),
location.getLine() + 1, // Display as 1-indexed
location.getColumn() + 1
);
} else {
System.err.printf("Error in %s%n", resource.getDescription());
}
}
}
public void checkPropertyOrigin(ConfigurableEnvironment environment, String propertyName) {
// Get the property value
Object value = environment.getProperty(propertyName);
// Try to find its origin
Origin origin = Origin.from(value);
if (origin != null) {
System.out.println("Property '" + propertyName + "' comes from: " + origin);
// Check parent origins
List<Origin> parents = Origin.parentsFrom(value);
for (Origin parent : parents) {
System.out.println(" Parent origin: " + parent);
}
} else {
System.out.println("Property '" + propertyName + "' has unknown origin");
}
}
}Property Source Reading: When Spring Boot reads property files (YAML, properties, etc.), it wraps property values in origin-tracking wrappers.
OriginTrackedValue: Values are wrapped as OriginTrackedValue which implements OriginProvider.
Bind Exceptions: When property binding fails, Spring Boot includes origin information in the exception message.
ConfigData Integration: ConfigDataResource implementations can provide origins through the OriginTrackedResource interface.
Spring Boot automatically uses origin tracking to provide helpful error messages:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'server.port' to int failed:
Property: server.port
Value: "not-a-number"
Origin: class path resource [application.properties] - 10:14
Reason: failed to convert java.lang.String to int
Action:
Update your application's configurationThe Origin: class path resource [application.properties] - 10:14 line tells you exactly where the problematic property was defined (line 10, column 14).
Load secrets from HashiCorp Vault using custom ConfigData components.
import org.springframework.boot.context.config.*;
import org.springframework.core.env.PropertySource;
import java.io.IOException;
import java.util.*;
// Custom ConfigData location for Vault
public class VaultConfigDataLocation extends ConfigDataLocation {
private final String path;
public VaultConfigDataLocation(String path) {
this.path = path;
}
public String getPath() {
return path;
}
}
// Resolver for vault: prefix
public class VaultConfigDataLocationResolver
implements ConfigDataLocationResolver<VaultConfigDataResource> {
@Override
public boolean isResolvable(ConfigDataLocationResolverContext context,
ConfigDataLocation location) {
return location.toString().startsWith("vault:");
}
@Override
public List<VaultConfigDataResource> resolve(
ConfigDataLocationResolverContext context,
ConfigDataLocation location) {
String path = location.toString().substring(6); // Remove "vault:"
return Collections.singletonList(new VaultConfigDataResource(path));
}
}
// Loader for Vault resources
public class VaultConfigDataLoader
implements ConfigDataLoader<VaultConfigDataResource> {
private final VaultClient vaultClient;
public VaultConfigDataLoader() {
this.vaultClient = new VaultClient();
}
@Override
public ConfigData load(ConfigDataLoaderContext context,
VaultConfigDataResource resource) throws IOException {
Map<String, Object> secrets = vaultClient.readSecrets(resource.getPath());
PropertySource<?> ps = new MapPropertySource("vault:" + resource.getPath(), secrets);
return new ConfigData(Collections.singleton(ps));
}
}Register in META-INF/spring.factories:
org.springframework.boot.context.config.ConfigDataLocationResolver=\
com.example.config.VaultConfigDataLocationResolver
org.springframework.boot.context.config.ConfigDataLoader=\
com.example.config.VaultConfigDataLoaderUse in application.yml:
spring:
config:
import: vault://secret/myappLoad configuration from Kubernetes ConfigMaps using configtree.
spring:
config:
import:
- "optional:configtree:/etc/config/"
- "optional:configtree:/etc/secrets/"Kubernetes ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
database.url: "jdbc:postgresql://db:5432/mydb"
database.username: "appuser"
---
apiVersion: v1
kind: Secret
metadata:
name: myapp-secrets
data:
database.password: ZGJwYXNzd29yZA==spring:
config:
import:
- "optional:file:./config/local.yml"
- "classpath:defaults.yml"
- "optional:configserver:http://config-server:8888"Implement profile-aware custom configuration.
public class ProfileAwareResolver
implements ConfigDataLocationResolver<ProfileAwareResource> {
@Override
public List<ProfileAwareResource> resolveProfileSpecific(
ConfigDataLocationResolverContext context,
ConfigDataLocation location,
Profiles profiles) {
List<ProfileAwareResource> resources = new ArrayList<>();
resources.add(new ProfileAwareResource("base", false));
for (String profile : profiles.getAccepted()) {
resources.add(new ProfileAwareResource(profile, true));
}
return resources;
}
}public class ResilientConfigDataLoader
implements ConfigDataLoader<ResilientResource> {
@Override
public ConfigData load(ConfigDataLoaderContext context,
ResilientResource resource) throws IOException {
try {
return loadWithRetry(resource, 3);
} catch (Exception e) {
if (resource.isOptional()) {
return ConfigData.EMPTY;
}
throw new ConfigDataNotFoundException(resource, e);
}
}
private ConfigData loadWithRetry(ResilientResource resource, int maxAttempts)
throws IOException {
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
Map<String, Object> props = fetchConfiguration(resource);
PropertySource<?> ps = new MapPropertySource(resource.toString(), props);
return new ConfigData(Collections.singleton(ps));
} catch (IOException e) {
if (attempt < maxAttempts) {
Thread.sleep(1000 * attempt); // Exponential backoff
} else {
throw e;
}
}
}
throw new IOException("Failed after " + maxAttempts + " attempts");
}
}Resource Uniqueness: Ensure ConfigDataResource implementations have proper equals(), hashCode(), and toString() methods.
Optional Locations: Use the optional: prefix for configuration locations that may not exist:
spring.config.import=optional:encrypted:/etc/config/secrets.encProfile-Specific Loading: Implement resolveProfileSpecific() in resolvers to support profile-based configuration.
Error Handling: Throw appropriate exceptions:
ConfigDataLocationNotFoundException for missing locationsConfigDataResourceNotFoundException for missing resourcesIOException for I/O errorsBootstrap Context: Use ConfigurableBootstrapContext to share expensive objects between resolvers and loaders.
Deferred Logging: Accept DeferredLogFactory in constructors to ensure logging is available at the right time.
Ordering: Implement Ordered or use @Order to control when resolvers are consulted.
Property Source Options: Use ConfigData.Option to control how property sources are processed:
IGNORE_IMPORTS: Don't process spring.config.import from this sourceIGNORE_PROFILES: Don't process profile properties from this sourcePROFILE_SPECIFIC: Mark source as profile-specificThread Safety: All ConfigData components must be thread-safe as they're called concurrently
Testing: Test custom ConfigData loaders with various scenarios including missing resources and malformed data
org.springframework.boot.context.configorg.springframework.boot.env