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.
| Annotation | Purpose | Thread Safety | Key Features |
|---|---|---|---|
@ConfigurationProperties | Bind external properties to objects | Thread-safe (Spring managed) | Prefix-based binding, relaxed naming, type conversion |
@EnableConfigurationProperties | Enable specific properties classes | N/A (meta-annotation) | Explicit registration, component scanning alternative |
@ConfigurationPropertiesScan | Auto-scan for properties classes | N/A (meta-annotation) | Package-level scanning, base package specification |
@ConstructorBinding | Use constructor for binding | N/A (binding mode) | Immutable properties, record support |
@NestedConfigurationProperty | Mark nested property objects | N/A (marker) | Complex object hierarchies |
@DeprecatedConfigurationProperty | Mark deprecated properties | N/A (documentation) | Migration guidance, IDE warnings |
@Validated | Enable JSR-303 validation | N/A (validation trigger) | Startup-time validation, fail-fast |
| Component | Purpose | Thread Safety | Key Use Cases |
|---|---|---|---|
Binder | Core property binding engine | Thread-safe | Programmatic binding, custom sources |
BindResult<T> | Binding operation result | Thread-safe (immutable) | Success/failure handling, value extraction |
Bindable<T> | Target type descriptor | Thread-safe (immutable) | Generic types, nested bindings |
BindHandler | Binding lifecycle callbacks | Implementation dependent | Validation, transformation, logging |
PropertyMapper | Fluent property mapping | Not thread-safe | Third-party configuration, conditional mapping |
ConfigurationPropertySource | Property source abstraction | Thread-safe | Custom property sources, name resolution |
| Handler | Purpose | Thread Safety | Behavior |
|---|---|---|---|
IgnoreErrorsBindHandler | Ignore binding errors | Thread-safe | Continue on error, use defaults |
IgnoreTopLevelConverterNotFoundBindHandler | Ignore missing converters | Thread-safe | Allow partial binding |
NoUnboundElementsBindHandler | Fail on unknown properties | Thread-safe | Strict validation |
ValidationBindHandler | JSR-303 validation | Thread-safe | Fail on validation errors |
| Method | Purpose | Thread Safety | Returns |
|---|---|---|---|
get(ApplicationContext, Object, String) | Get bean for instance | Thread-safe | ConfigurationPropertiesBean or null |
getAll(ApplicationContext) | Get all properties beans | Thread-safe | Map of bean name to ConfigurationPropertiesBean |
forValueObject(Class, String) | Create for value object | Thread-safe | ConfigurationPropertiesBean |
| Format | Example | Supported Contexts |
|---|---|---|
| Kebab case | my-property | Properties files (recommended) |
| Camel case | myProperty | Properties files, YAML |
| Underscore | my_property | Environment variables |
| Upper case | MY_PROPERTY | Environment variables |
Spring Boot's Configuration Properties binding provides a type-safe way to bind external configuration (properties files, environment variables, command-line arguments) to Java objects. This system consists of annotations for marking configuration classes, a powerful binding API, and utilities for mapping properties.
Package: org.springframework.boot.context.properties
Core Binding Package: org.springframework.boot.context.properties.bind
Marks a class or @Bean method for external configuration binding.
package org.springframework.boot.context.properties;
/**
* Annotation for externalized configuration. Add this to a class definition or a
* @Bean method in a @Configuration class if you want to bind and validate
* some external Properties (e.g. from a .properties file).
*
* Binding is either performed by calling setters on the annotated class or, if
* @ConstructorBinding is in use, by binding to the constructor parameters.
*
* Note that contrary to @Value, SpEL expressions are not evaluated since property
* values are externalized.
*
* @since 1.0.0
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface ConfigurationProperties {
/**
* The prefix of the properties that are valid to bind to this object.
* Synonym for prefix(). A valid prefix is defined by one or more words
* separated with dots (e.g. "acme.system.feature").
*
* @return the prefix of the properties to bind
*/
@AliasFor("prefix")
String value() default "";
/**
* The prefix of the properties that are valid to bind to this object.
* Synonym for value(). A valid prefix is defined by one or more words
* separated with dots (e.g. "acme.system.feature").
*
* @return the prefix of the properties to bind
*/
@AliasFor("value")
String prefix() default "";
/**
* Flag to indicate that when binding to this object invalid fields should
* be ignored. Invalid means invalid according to the binder that is used,
* and usually this means fields of the wrong type (or that cannot be
* coerced into the correct type).
*
* @return the flag value (default false)
*/
boolean ignoreInvalidFields() default false;
/**
* Flag to indicate that when binding to this object unknown fields should
* be ignored. An unknown field could be a sign of a mistake in the Properties.
*
* @return the flag value (default true)
*/
boolean ignoreUnknownFields() default true;
}Usage Example:
@ConfigurationProperties(prefix = "app.database")
public class DatabaseProperties {
private String url;
private String username;
private String password;
private int maxConnections = 10;
// Getters and setters
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getMaxConnections() { return maxConnections; }
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
}Properties file:
app.database.url=jdbc:postgresql://localhost:5432/mydb
app.database.username=admin
app.database.password=secret
app.database.max-connections=20Enables support for @ConfigurationProperties beans and optionally registers specific classes.
package org.springframework.boot.context.properties;
/**
* Enable support for @ConfigurationProperties annotated beans.
* @ConfigurationProperties beans can be registered in the standard way
* (for example using @Bean methods) or, for convenience, can be specified
* directly on this annotation.
*
* @since 1.0.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
/**
* The bean name of the configuration properties validator.
* @since 2.2.0
*/
String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
/**
* Convenient way to quickly register @ConfigurationProperties annotated
* beans with Spring. Standard Spring Beans will also be scanned regardless
* of this value.
*
* @return @ConfigurationProperties annotated beans to register
*/
Class<?>[] value() default {};
}Usage Example:
@Configuration
@EnableConfigurationProperties({DatabaseProperties.class, CacheProperties.class})
public class AppConfiguration {
// Configuration beans
}Configures base packages for scanning @ConfigurationProperties classes.
package org.springframework.boot.context.properties;
/**
* Configures the base packages used when scanning for
* @ConfigurationProperties classes. One of basePackageClasses(), basePackages()
* or its alias value() may be specified to define specific packages to scan.
* If specific packages are not defined scanning will occur from the package
* of the class with this annotation.
*
* Note: Classes annotated or meta-annotated with @Component will not be
* picked up by this annotation.
*
* @since 2.2.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ConfigurationPropertiesScanRegistrar.class)
@EnableConfigurationProperties
public @interface ConfigurationPropertiesScan {
/**
* Alias for the basePackages() attribute. Allows for more concise annotation
* declarations e.g.: @ConfigurationPropertiesScan("org.my.pkg") instead of
* @ConfigurationPropertiesScan(basePackages="org.my.pkg").
*
* @return the base packages to scan
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* Base packages to scan for configuration properties. value() is an alias
* for (and mutually exclusive with) this attribute.
*
* Use basePackageClasses() for a type-safe alternative to String-based
* package names.
*
* @return the base packages to scan
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* Type-safe alternative to basePackages() for specifying the packages to
* scan for configuration properties. The package of each class specified
* will be scanned.
*
* Consider creating a special no-op marker class or interface in each
* package that serves no purpose other than being referenced by this attribute.
*
* @return classes from the base packages to scan
*/
Class<?>[] basePackageClasses() default {};
}Usage Example:
@SpringBootApplication
@ConfigurationPropertiesScan("com.example.config")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}Indicates that a property should be treated as a nested type.
package org.springframework.boot.context.properties;
/**
* Indicates that a property in a @ConfigurationProperties object should be
* treated as if it were a nested type. This annotation has no bearing on
* the actual binding processes, but it is used by the
* spring-boot-configuration-processor as a hint that a property is not bound
* as a single value. When this is specified, a nested group is created for
* the property and its type is harvested.
*
* This has no effect on collections and maps as these types are automatically
* identified. Also, the annotation is not necessary if the target type is an
* inner class of the @ConfigurationProperties object.
*
* @since 1.2.0
*/
@Target({ ElementType.FIELD, ElementType.RECORD_COMPONENT, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Nested
public @interface NestedConfigurationProperty {
}Usage Example:
@ConfigurationProperties("app.server")
public class ServerProperties {
@NestedConfigurationProperty
private final Host host = new Host();
private int port = 8080;
public Host getHost() { return host; }
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public static class Host {
private String name;
private String ip;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getIp() { return ip; }
public void setIp(String ip) { this.ip = ip; }
}
}Properties file:
app.server.port=9090
app.server.host.name=localhost
app.server.host.ip=127.0.0.1Indicates that constructor binding should be used instead of JavaBean binding.
package org.springframework.boot.context.properties.bind;
/**
* Annotation that can be used to indicate which constructor to use when binding
* configuration properties using constructor arguments rather than by calling
* setters. A single parameterized constructor implicitly indicates that
* constructor binding should be used unless the constructor is annotated
* with @Autowired.
*
* @since 3.0.0
*/
@Target({ ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConstructorBinding {
}Usage Example:
@ConfigurationProperties("app.service")
public class ServiceProperties {
private final String name;
private final int timeout;
private final boolean enabled;
@ConstructorBinding
public ServiceProperties(String name, int timeout, boolean enabled) {
this.name = name;
this.timeout = timeout;
this.enabled = enabled;
}
public String getName() { return name; }
public int getTimeout() { return timeout; }
public boolean isEnabled() { return enabled; }
}With Java Records (no annotation needed):
@ConfigurationProperties("app.service")
public record ServiceProperties(String name, int timeout, boolean enabled) {
}Marks a configuration property as deprecated for documentation purposes.
package org.springframework.boot.context.properties;
/**
* Indicates that a getter in a @ConfigurationProperties object is deprecated.
* This annotation has no bearing on the actual binding processes, but it is
* used by the spring-boot-configuration-processor to add deprecation meta-data.
*
* This annotation must be used on the getter of the deprecated element.
*
* @since 1.3.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DeprecatedConfigurationProperty {
/**
* The reason for the deprecation.
* @return the deprecation reason
*/
String reason() default "";
/**
* The field that should be used instead (if any).
* @return the replacement field
*/
String replacement() default "";
/**
* The version in which the property became deprecated.
* @return the version
*/
String since() default "";
}Usage Example:
@ConfigurationProperties("app.legacy")
public class LegacyProperties {
private String oldUrl;
private String newUrl;
@DeprecatedConfigurationProperty(
replacement = "app.legacy.new-url",
reason = "Use newUrl for better configuration",
since = "2.0.0"
)
public String getOldUrl() { return oldUrl; }
public void setOldUrl(String oldUrl) { this.oldUrl = oldUrl; }
public String getNewUrl() { return newUrl; }
public void setNewUrl(String newUrl) { this.newUrl = newUrl; }
}Qualifier annotation for beans needed to configure configuration properties binding, such as custom converters.
package org.springframework.boot.context.properties;
/**
* Qualifier for beans that are needed to configure the binding of
* @ConfigurationProperties (e.g. Converters). Bean methods that declare a
* @ConfigurationPropertiesBinding bean should be static to ensure that
* "bean is not eligible for getting processed by all BeanPostProcessors"
* warnings are not produced.
*
* @since 1.3.0
*/
@Qualifier(ConfigurationPropertiesBinding.VALUE)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationPropertiesBinding {
String VALUE = "org.springframework.boot.context.properties.ConfigurationPropertiesBinding";
}Usage Example:
@Configuration
public class CustomConverterConfiguration {
@Bean
@ConfigurationPropertiesBinding
public static Converter<String, CustomType> customTypeConverter() {
return new Converter<String, CustomType>() {
@Override
public CustomType convert(String source) {
return CustomType.parse(source);
}
};
}
@Bean
@ConfigurationPropertiesBinding
public static GenericConverter customGenericConverter() {
return new GenericConverter() {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Set.of(new ConvertiblePair(String.class, ComplexType.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
return ComplexType.fromString((String) source);
}
};
}
}Spring Boot provides several exception classes for handling configuration property binding failures.
package org.springframework.boot.context.properties;
/**
* Exception thrown when @ConfigurationProperties binding fails.
*
* @since 2.0.0
*/
public class ConfigurationPropertiesBindException extends BeanCreationException {
/**
* Creates a new exception for the given bean and cause.
*
* @param bean the configuration properties bean
* @param cause the underlying cause
*/
public ConfigurationPropertiesBindException(ConfigurationPropertiesBean bean, Exception cause);
/**
* Returns the type of the bean that failed to bind.
*
* @return the bean type
*/
public Class<?> getBeanType();
/**
* Returns the @ConfigurationProperties annotation from the bean.
*
* @return the annotation
*/
public ConfigurationProperties getAnnotation();
}Usage:
This exception is thrown automatically by Spring Boot when configuration property binding fails. Catch it to handle binding errors gracefully:
try {
context.getBean(MyProperties.class);
} catch (ConfigurationPropertiesBindException ex) {
Class<?> beanType = ex.getBeanType();
ConfigurationProperties annotation = ex.getAnnotation();
System.err.println("Failed to bind " + annotation.prefix() + " to " + beanType.getName());
}package org.springframework.boot.context.properties;
/**
* Exception thrown when the application has configured an incompatible set
* of @ConfigurationProperties keys.
*
* @since 2.4.0
*/
public class IncompatibleConfigurationException extends RuntimeException {
/**
* Creates a new exception for the given incompatible keys.
*
* @param incompatibleKeys the incompatible configuration keys
*/
public IncompatibleConfigurationException(String... incompatibleKeys);
/**
* Returns the incompatible configuration keys.
*
* @return the incompatible keys
*/
public Collection<String> getIncompatibleKeys();
}Usage:
Throw this exception when multiple incompatible properties are configured:
@ConfigurationProperties("app")
public class AppProperties {
private String legacyUrl;
private String newUrl;
@PostConstruct
public void validate() {
if (legacyUrl != null && newUrl != null) {
throw new IncompatibleConfigurationException(
"app.legacy-url",
"app.new-url"
);
}
}
}package org.springframework.boot.context.properties.source;
/**
* Exception thrown when more than one mutually exclusive configuration property
* has been configured.
*
* @since 2.6.0
*/
public class MutuallyExclusiveConfigurationPropertiesException extends RuntimeException {
/**
* Creates a new exception for the given configured and mutually exclusive names.
*
* @param configuredNames the names that have been configured
* @param mutuallyExclusiveNames all the mutually exclusive names
*/
public MutuallyExclusiveConfigurationPropertiesException(
Collection<String> configuredNames,
Collection<String> mutuallyExclusiveNames);
/**
* Returns the names that have been configured.
*
* @return the configured names
*/
public Set<String> getConfiguredNames();
/**
* Returns all the names that are mutually exclusive.
*
* @return the mutually exclusive names
*/
public Set<String> getMutuallyExclusiveNames();
/**
* Throws an exception if multiple non-null values are present in the entries.
*
* @param entries a consumer that accepts a map to populate with name/value pairs
* @throws MutuallyExclusiveConfigurationPropertiesException if multiple non-null values
*/
public static void throwIfMultipleNonNullValuesIn(
Consumer<Map<String, @Nullable Object>> entries);
/**
* Throws an exception if multiple values matching the predicate are present.
*
* @param entries a consumer that accepts a map to populate with name/value pairs
* @param predicate the predicate to test values
* @param <V> the value type
* @throws MutuallyExclusiveConfigurationPropertiesException if multiple matching values
* @since 3.3.7
*/
public static <V> void throwIfMultipleMatchingValuesIn(
Consumer<Map<String, @Nullable V>> entries,
Predicate<@Nullable V> predicate);
}Usage:
Validate mutually exclusive properties:
@ConfigurationProperties("app.database")
public class DatabaseProperties {
private String jdbcUrl;
private DataSourceConfig datasource;
private JndiConfig jndi;
@PostConstruct
public void validate() {
MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn(
(entries) -> {
entries.put("app.database.jdbc-url", jdbcUrl);
entries.put("app.database.datasource", datasource);
entries.put("app.database.jndi", jndi);
}
);
}
}package org.springframework.boot.context.properties.bind;
/**
* BindException thrown when ConfigurationPropertySource elements were left unbound.
*
* @since 2.0.0
*/
public class UnboundConfigurationPropertiesException extends RuntimeException {
/**
* Creates a new exception for the given unbound properties.
*
* @param unboundProperties the properties that were not bound
*/
public UnboundConfigurationPropertiesException(Set<ConfigurationProperty> unboundProperties);
/**
* Returns the properties that were not bound.
*
* @return the unbound properties as an immutable set
*/
public Set<ConfigurationProperty> getUnboundProperties();
}Usage:
This exception is thrown when strict binding is enabled and properties remain unbound:
import org.springframework.boot.context.properties.bind.*;
Binder binder = Binder.get(environment);
BindHandler handler = new NoUnboundElementsBindHandler();
try {
MyProperties props = binder.bind("app", Bindable.of(MyProperties.class), handler)
.get();
} catch (UnboundConfigurationPropertiesException ex) {
Set<ConfigurationProperty> unbound = ex.getUnboundProperties();
unbound.forEach(prop -> {
System.err.println("Unbound property: " + prop.getName());
});
}package org.springframework.boot.context.properties;
/**
* Indicates that the annotated type is a source of configuration properties metadata.
* Serves as a hint to the spring-boot-configuration-processor to generate full
* metadata for the type.
*
* @since 4.0.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationPropertiesSource {
}Usage:
Use this annotation on types that provide configuration metadata but are in different modules:
@ConfigurationPropertiesSource
public class ServerConfig {
private int port = 8080;
private String host = "localhost";
// Getters and setters
}
@ConfigurationProperties("app.server")
public class AppServerProperties {
private ServerConfig config = new ServerConfig();
public ServerConfig getConfig() {
return config;
}
public void setConfig(ServerConfig config) {
this.config = config;
}
}The source package provides low-level access to configuration properties before binding.
package org.springframework.boot.context.properties.source;
/**
* A source of ConfigurationProperties.
*
* @since 2.0.0
*/
@FunctionalInterface
public interface ConfigurationPropertySource {
/**
* Returns a single configuration property from the source.
*
* @param name the property name
* @return the configuration property or null
*/
@Nullable
ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name);
/**
* Returns the state of any descendants of the given name.
*
* @param name the name to check
* @return the property state
*/
default ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name);
/**
* Returns a filtered variant of this source.
*
* @param filter the filter to apply
* @return the filtered source
*/
default ConfigurationPropertySource filter(Predicate<ConfigurationPropertyName> filter);
/**
* Returns a variant of this source with name aliases.
*
* @param aliases the aliases to apply
* @return the source with aliases
*/
default ConfigurationPropertySource withAliases(ConfigurationPropertyNameAliases aliases);
/**
* Returns a variant of this source with a prefix.
*
* @param prefix the prefix to add
* @return the source with prefix
* @since 2.5.0
*/
default ConfigurationPropertySource withPrefix(@Nullable String prefix);
/**
* Returns the underlying source object.
*
* @return the underlying source or null
*/
default @Nullable Object getUnderlyingSource();
/**
* Creates a ConfigurationPropertySource from a PropertySource.
*
* @param source the property source
* @return the configuration property source or null
* @since 2.4.0
*/
static @Nullable ConfigurationPropertySource from(PropertySource<?> source);
}package org.springframework.boot.context.properties.source;
import java.util.Iterator;
import java.util.List;
/**
* Maintains a mapping of ConfigurationPropertyName aliases.
* Allows configuration properties to be accessed using alternative names.
*
* @since 2.0.0
*/
public final class ConfigurationPropertyNameAliases implements Iterable<ConfigurationPropertyName> {
/**
* Creates an empty set of aliases.
*/
public ConfigurationPropertyNameAliases();
/**
* Creates aliases with an initial mapping.
*
* @param name the configuration property name
* @param aliases the aliases for the name
*/
public ConfigurationPropertyNameAliases(String name, String... aliases);
/**
* Creates aliases with an initial mapping.
*
* @param name the configuration property name
* @param aliases the aliases for the name
*/
public ConfigurationPropertyNameAliases(ConfigurationPropertyName name, ConfigurationPropertyName... aliases);
/**
* Adds aliases for a configuration property name.
*
* @param name the configuration property name
* @param aliases the aliases to add
*/
public void addAliases(String name, String... aliases);
/**
* Adds aliases for a configuration property name.
*
* @param name the configuration property name
* @param aliases the aliases to add
*/
public void addAliases(ConfigurationPropertyName name, ConfigurationPropertyName... aliases);
/**
* Gets all aliases for the given name.
*
* @param name the configuration property name
* @return list of aliases (may be empty)
*/
public List<ConfigurationPropertyName> getAliases(ConfigurationPropertyName name);
/**
* Gets the name for the given alias.
*
* @param alias the alias
* @return the name or null if no name is found
*/
public ConfigurationPropertyName getNameForAlias(ConfigurationPropertyName alias);
@Override
public Iterator<ConfigurationPropertyName> iterator();
}Usage Example:
import org.springframework.boot.context.properties.source.*;
// Create aliases for legacy property names
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
aliases.addAliases("server.port", "port", "server.http.port");
aliases.addAliases("spring.datasource.url", "database.url", "db.url");
// Apply aliases to a configuration property source
ConfigurationPropertySource source = ConfigurationPropertySources.get(environment).iterator().next();
ConfigurationPropertySource aliasedSource = source.withAliases(aliases);
// Now properties can be accessed via their aliases
// "port" will resolve to "server.port"
// "database.url" will resolve to "spring.datasource.url"Common Use Cases:
package org.springframework.boot.context.properties.source;
/**
* A single configuration property obtained from a ConfigurationPropertySource
* consisting of a name, value and optional origin.
*
* @since 2.0.0
*/
public final class ConfigurationProperty
implements OriginProvider, Comparable<ConfigurationProperty> {
/**
* Creates a new configuration property.
*
* @param name the property name
* @param value the property value
* @param origin the property origin (may be null)
*/
public ConfigurationProperty(
ConfigurationPropertyName name,
Object value,
@Nullable Origin origin);
/**
* Returns the configuration property source that provided this property.
*
* @return the source or null
* @since 2.6.0
*/
public @Nullable ConfigurationPropertySource getSource();
/**
* Returns the name of the property.
*
* @return the property name
*/
public ConfigurationPropertyName getName();
/**
* Returns the value of the property.
*
* @return the property value
*/
public Object getValue();
/**
* Returns the origin of the property.
*
* @return the origin or null
*/
@Override
public @Nullable Origin getOrigin();
@Override
public int compareTo(ConfigurationProperty other);
}package org.springframework.boot.context.properties.source;
/**
* A configuration property name composed of elements separated by dots.
* User created names may contain "a-z", "0-9", "-", must be lower-case
* and must start with an alphanumeric character.
*
* @since 2.0.0
*/
public final class ConfigurationPropertyName
implements Comparable<ConfigurationPropertyName> {
/**
* An empty configuration property name.
*/
public static final ConfigurationPropertyName EMPTY;
/**
* Returns true if the name is empty.
*
* @return true if empty
*/
public boolean isEmpty();
/**
* Returns true if the last element is indexed.
*
* @return true if indexed
*/
public boolean isLastElementIndexed();
/**
* Returns true if the name contains any indexed elements.
*
* @return true if has indexed element
* @since 2.2.10
*/
public boolean hasIndexedElement();
/**
* Returns true if the specified element is indexed.
*
* @param elementIndex the element index
* @return true if indexed
*/
boolean isIndexed(int elementIndex);
/**
* Returns true if the element contains only digits.
*
* @param elementIndex the element index
* @return true if numeric
*/
public boolean isNumericIndex(int elementIndex);
/**
* Returns the last element in the specified form.
*
* @param form the form to use
* @return the last element
*/
public String getLastElement(Form form);
/**
* Returns the element at the specified index in the specified form.
*
* @param elementIndex the element index
* @param form the form to use
* @return the element
*/
public String getElement(int elementIndex, Form form);
/**
* Returns the total number of elements.
*
* @return the number of elements
*/
public int getNumberOfElements();
/**
* Returns a new name by appending the suffix.
*
* @param suffix the suffix to append
* @return the new name
*/
public ConfigurationPropertyName append(@Nullable String suffix);
/**
* Returns a new name by appending another name.
*
* @param suffix the name to append
* @return the new name
* @since 2.5.0
*/
public ConfigurationPropertyName append(@Nullable ConfigurationPropertyName suffix);
/**
* Returns the parent of this name.
*
* @return the parent or null
*/
public ConfigurationPropertyName getParent();
/**
* Returns a new name chopped to the specified size.
*
* @param size the size to chop to
* @return the chopped name
*/
public ConfigurationPropertyName chop(int size);
/**
* Returns a sub-name starting at the offset.
*
* @param offset the starting offset
* @return the sub-name
* @since 2.5.0
*/
public ConfigurationPropertyName subName(int offset);
/**
* Returns true if this name is a parent of the specified name.
*
* @param name the name to check
* @return true if parent
*/
public boolean isParentOf(ConfigurationPropertyName name);
/**
* Returns true if this name is an ancestor of the specified name.
*
* @param name the name to check
* @return true if ancestor
*/
public boolean isAncestorOf(ConfigurationPropertyName name);
@Override
public int compareTo(ConfigurationPropertyName other);
/**
* Checks if a character sequence is a valid configuration property name.
*
* @param name the name to check
* @return true if valid
*/
public static boolean isValid(@Nullable CharSequence name);
/**
* Creates a name from a character sequence.
*
* @param name the name
* @return the configuration property name
* @throws InvalidConfigurationPropertyNameException if invalid
*/
public static ConfigurationPropertyName of(@Nullable CharSequence name);
/**
* Creates a name if valid, otherwise returns null.
*
* @param name the name
* @return the configuration property name or null
* @since 2.3.1
*/
public static @Nullable ConfigurationPropertyName ofIfValid(@Nullable CharSequence name);
/**
* Adapts a name using a custom separator.
*
* @param name the name to adapt
* @param separator the separator character
* @return the adapted name
*/
public static ConfigurationPropertyName adapt(CharSequence name, char separator);
/**
* The form used for a configuration property name element.
*/
public enum Form {
/**
* The original form as specified.
*/
ORIGINAL,
/**
* Lower-case with only alphanumeric and dashes.
*/
DASHED,
/**
* Lower-case with only alphanumeric (for equals/hashCode).
*/
UNIFORM
}
}Usage Example:
import org.springframework.boot.context.properties.source.*;
import org.springframework.core.env.Environment;
@Component
public class ConfigPropertyInspector {
private final Environment environment;
public ConfigPropertyInspector(Environment environment) {
this.environment = environment;
}
public void inspectProperty(String propertyName) {
ConfigurationPropertyName name = ConfigurationPropertyName.of(propertyName);
// Get property sources
Iterable<ConfigurationPropertySource> sources =
ConfigurationPropertySources.get(environment);
for (ConfigurationPropertySource source : sources) {
ConfigurationProperty property = source.getConfigurationProperty(name);
if (property != null) {
System.out.println("Property: " + property.getName());
System.out.println("Value: " + property.getValue());
System.out.println("Origin: " + property.getOrigin());
System.out.println("Source: " + property.getSource());
break;
}
}
}
}The Binder class provides the low-level binding API for converting configuration properties to typed objects.
package org.springframework.boot.context.properties.bind;
/**
* A container object which Binds objects from one or more
* ConfigurationPropertySources.
*
* @since 2.0.0
*/
public class Binder {
/**
* Create a new Binder instance for the specified sources. A
* DefaultFormattingConversionService will be used for all conversion.
*
* @param sources the sources used for binding
*/
public Binder(ConfigurationPropertySource... sources);
/**
* Create a new Binder instance for the specified sources. A
* DefaultFormattingConversionService will be used for all conversion.
*
* @param sources the sources used for binding
*/
public Binder(Iterable<ConfigurationPropertySource> sources);
/**
* Create a new Binder instance for the specified sources.
*
* @param sources the sources used for binding
* @param placeholdersResolver strategy to resolve any property placeholders
*/
public Binder(Iterable<ConfigurationPropertySource> sources,
@Nullable PlaceholdersResolver placeholdersResolver);
/**
* Create a new Binder instance for the specified sources.
*
* @param sources the sources used for binding
* @param placeholdersResolver strategy to resolve any property placeholders
* @param conversionService the conversion service to convert values (or null
* to use ApplicationConversionService)
*/
public Binder(Iterable<ConfigurationPropertySource> sources,
@Nullable PlaceholdersResolver placeholdersResolver,
@Nullable ConversionService conversionService);
/**
* Create a new Binder instance for the specified sources.
*
* @param sources the sources used for binding
* @param placeholdersResolver strategy to resolve any property placeholders
* @param conversionService the conversion service to convert values (or null
* to use ApplicationConversionService)
* @param propertyEditorInitializer initializer used to configure the property
* editors that can convert values (or null if
* no initialization is required)
* @since 2.0.0
*/
public Binder(Iterable<ConfigurationPropertySource> sources,
@Nullable PlaceholdersResolver placeholdersResolver,
@Nullable ConversionService conversionService,
Consumer<PropertyEditorRegistry> propertyEditorInitializer);
/**
* Create a new Binder instance for the specified sources.
*
* @param sources the sources used for binding
* @param placeholdersResolver strategy to resolve any property placeholders
* @param conversionService the conversion service to convert values (or null
* to use ApplicationConversionService)
* @param propertyEditorInitializer initializer used to configure the property
* editors that can convert values (or null if
* no initialization is required)
* @param defaultBindHandler the default bind handler to use if none is specified
* when binding
* @since 2.2.0
*/
public Binder(Iterable<ConfigurationPropertySource> sources,
@Nullable PlaceholdersResolver placeholdersResolver,
@Nullable ConversionService conversionService,
Consumer<PropertyEditorRegistry> propertyEditorInitializer,
BindHandler defaultBindHandler);
/**
* Create a new Binder instance for the specified sources.
*
* @param sources the sources used for binding
* @param placeholdersResolver strategy to resolve any property placeholders
* @param conversionService the conversion service to convert values (or null
* to use ApplicationConversionService)
* @param propertyEditorInitializer initializer used to configure the property
* editors that can convert values (or null if
* no initialization is required)
* @param defaultBindHandler the default bind handler to use if none is specified
* when binding
* @param constructorProvider the constructor provider which provides the bind
* constructor to use when binding
* @since 2.2.1
*/
public Binder(Iterable<ConfigurationPropertySource> sources,
@Nullable PlaceholdersResolver placeholdersResolver,
@Nullable ConversionService conversionService,
Consumer<PropertyEditorRegistry> propertyEditorInitializer,
BindHandler defaultBindHandler,
BindConstructorProvider constructorProvider);
/**
* Create a new Binder instance for the specified sources.
*
* @param sources the sources used for binding
* @param placeholdersResolver strategy to resolve any property placeholders
* @param conversionServices the conversion services to convert values (or null
* to use ApplicationConversionService)
* @param propertyEditorInitializer initializer used to configure the property
* editors that can convert values (or null if
* no initialization is required)
* @param defaultBindHandler the default bind handler to use if none is specified
* when binding
* @param constructorProvider the constructor provider which provides the bind
* constructor to use when binding
* @since 2.5.0
*/
public Binder(Iterable<ConfigurationPropertySource> sources,
@Nullable PlaceholdersResolver placeholdersResolver,
@Nullable List<ConversionService> conversionServices,
Consumer<PropertyEditorRegistry> propertyEditorInitializer,
BindHandler defaultBindHandler,
BindConstructorProvider constructorProvider);
/**
* Bind the specified target Class using this binder's property sources.
*
* @param <T> the bound type
* @param name the configuration property name to bind
* @param target the target class
* @return the binding result (never null)
*/
public <T> BindResult<T> bind(String name, Class<T> target);
/**
* Bind the specified target Bindable using this binder's property sources.
*
* @param <T> the bound type
* @param name the configuration property name to bind
* @param target the target bindable
* @return the binding result (never null)
*/
public <T> BindResult<T> bind(String name, Bindable<T> target);
/**
* Bind the specified target Bindable using this binder's property sources.
*
* @param <T> the bound type
* @param name the configuration property name to bind
* @param target the target bindable
* @return the binding result (never null)
*/
public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target);
/**
* Bind the specified target Bindable using this binder's property sources.
*
* @param <T> the bound type
* @param name the configuration property name to bind
* @param target the target bindable
* @param handler the bind handler (may be null)
* @return the binding result (never null)
*/
public <T> BindResult<T> bind(String name, Bindable<T> target, BindHandler handler);
/**
* Bind the specified target Bindable using this binder's property sources.
*
* @param <T> the bound type
* @param name the configuration property name to bind
* @param target the target bindable
* @param handler the bind handler (may be null)
* @return the binding result (never null)
*/
public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target,
BindHandler handler);
/**
* Bind the specified target Class using this binder's property sources or
* create a new instance of the specified target Class if the result of the
* binding is null.
*
* @param <T> the bound type
* @param name the configuration property name to bind
* @param target the target class
* @return the bound or created object
* @since 2.2.0
*/
public <T> T bindOrCreate(String name, Class<T> target);
/**
* Bind the specified target Bindable using this binder's property sources or
* create a new instance using the type of the Bindable if the result of the
* binding is null.
*
* @param <T> the bound type
* @param name the configuration property name to bind
* @param target the target bindable
* @return the bound or created object
* @since 2.2.0
*/
public <T> T bindOrCreate(String name, Bindable<T> target);
/**
* Bind the specified target Bindable using this binder's property sources or
* create a new instance using the type of the Bindable if the result of the
* binding is null.
*
* @param <T> the bound type
* @param name the configuration property name to bind
* @param target the target bindable
* @param handler the bind handler
* @return the bound or created object
* @since 2.2.0
*/
public <T> T bindOrCreate(String name, Bindable<T> target, BindHandler handler);
/**
* Bind the specified target Bindable using this binder's property sources or
* create a new instance using the type of the Bindable if the result of the
* binding is null.
*
* @param <T> the bound or created type
* @param name the configuration property name to bind
* @param target the target bindable
* @param handler the bind handler (may be null)
* @return the bound or created object
* @since 2.2.0
*/
public <T> T bindOrCreate(ConfigurationPropertyName name, Bindable<T> target,
BindHandler handler);
/**
* Create a new Binder instance from the specified environment.
*
* @param environment the environment source (must have attached
* ConfigurationPropertySources)
* @return a Binder instance
*/
public static Binder get(Environment environment);
/**
* Create a new Binder instance from the specified environment.
*
* @param environment the environment source (must have attached
* ConfigurationPropertySources)
* @param defaultBindHandler the default bind handler to use if none is specified
* when binding
* @return a Binder instance
* @since 2.2.0
*/
public static Binder get(Environment environment, BindHandler defaultBindHandler);
}Usage Example:
// Simple binding
Environment environment = context.getEnvironment();
Binder binder = Binder.get(environment);
// Bind to a class
BindResult<DatabaseConfig> result = binder.bind("app.database", DatabaseConfig.class);
if (result.isBound()) {
DatabaseConfig config = result.get();
System.out.println("URL: " + config.getUrl());
}
// Bind with Bindable
Bindable<List<String>> bindable = Bindable.listOf(String.class);
BindResult<List<String>> servers = binder.bind("app.servers", bindable);
// Bind or create with defaults
DatabaseConfig config = binder.bindOrCreate("app.database", DatabaseConfig.class);Describes a type that can be bound.
package org.springframework.boot.context.properties.bind;
/**
* Source that can be bound by a Binder.
*
* @param <T> the source type
* @since 2.0.0
*/
public final class Bindable<T> {
/**
* Return the type of the item to bind.
* @return the type being bound
*/
public ResolvableType getType();
/**
* Return the boxed type of the item to bind.
* @return the boxed type for the item being bound
*/
public ResolvableType getBoxedType();
/**
* Return a supplier that provides the object value or null.
* @return the value or null
*/
public Supplier<T> getValue();
/**
* Return any associated annotations that could affect binding.
* @return the associated annotations
*/
public Annotation[] getAnnotations();
/**
* Return a single associated annotations that could affect binding.
*
* @param <A> the annotation type
* @param type annotation type
* @return the associated annotation or null
*/
public <A extends Annotation> A getAnnotation(Class<A> type);
/**
* Returns true if the specified bind restriction has been added.
*
* @param bindRestriction the bind restriction to check
* @return if the bind restriction has been added
* @since 2.5.0
*/
public boolean hasBindRestriction(BindRestriction bindRestriction);
/**
* Returns the BindMethod to be used to bind this bindable, or null if no
* specific binding method is required.
*
* @return the bind method or null
* @since 3.0.8
*/
public BindMethod getBindMethod();
/**
* Create an updated Bindable instance with the specified annotations.
*
* @param annotations the annotations
* @return an updated Bindable
*/
public Bindable<T> withAnnotations(Annotation... annotations);
/**
* Create an updated Bindable instance with an existing value.
* Implies that Java Bean binding will be used.
*
* @param existingValue the existing value
* @return an updated Bindable
*/
public Bindable<T> withExistingValue(T existingValue);
/**
* Create an updated Bindable instance with a value supplier.
*
* @param suppliedValue the supplier for the value
* @return an updated Bindable
*/
public Bindable<T> withSuppliedValue(Supplier<T> suppliedValue);
/**
* Create an updated Bindable instance with additional bind restrictions.
*
* @param additionalRestrictions any additional restrictions to apply
* @return an updated Bindable
* @since 2.5.0
*/
public Bindable<T> withBindRestrictions(BindRestriction... additionalRestrictions);
/**
* Create an updated Bindable instance with a specific bind method.
*
* @param bindMethod the method to use to bind the bindable
* @return an updated Bindable
* @since 3.0.8
*/
public Bindable<T> withBindMethod(BindMethod bindMethod);
/**
* Create a new Bindable of the type of the specified instance with an
* existing value equal to the instance.
*
* @param <T> the source type
* @param instance the instance (must not be null)
* @return a Bindable instance
*/
public static <T> Bindable<T> ofInstance(T instance);
/**
* Create a new Bindable of the specified type.
*
* @param <T> the source type
* @param type the type (must not be null)
* @return a Bindable instance
*/
public static <T> Bindable<T> of(Class<T> type);
/**
* Create a new Bindable List of the specified element type.
*
* @param <E> the element type
* @param elementType the list element type
* @return a Bindable instance
*/
public static <E> Bindable<List<E>> listOf(Class<E> elementType);
/**
* Create a new Bindable Set of the specified element type.
*
* @param <E> the element type
* @param elementType the set element type
* @return a Bindable instance
*/
public static <E> Bindable<Set<E>> setOf(Class<E> elementType);
/**
* Create a new Bindable Map of the specified key and value type.
*
* @param <K> the key type
* @param <V> the value type
* @param keyType the map key type
* @param valueType the map value type
* @return a Bindable instance
*/
public static <K, V> Bindable<Map<K, V>> mapOf(Class<K> keyType, Class<V> valueType);
/**
* Create a new Bindable of the specified type.
*
* @param <T> the source type
* @param type the type (must not be null)
* @return a Bindable instance
*/
public static <T> Bindable<T> of(ResolvableType type);
/**
* Restrictions that can be applied when binding values.
* @since 2.5.0
*/
public enum BindRestriction {
/**
* Do not bind direct ConfigurationProperty matches.
*/
NO_DIRECT_PROPERTY
}
}Usage Example:
// Simple type binding
Bindable<String> stringBindable = Bindable.of(String.class);
// Collection binding
Bindable<List<Integer>> listBindable = Bindable.listOf(Integer.class);
Bindable<Set<String>> setBindable = Bindable.setOf(String.class);
Bindable<Map<String, Integer>> mapBindable = Bindable.mapOf(String.class, Integer.class);
// Binding with existing instance
DatabaseConfig existing = new DatabaseConfig();
Bindable<DatabaseConfig> withExisting = Bindable.of(DatabaseConfig.class)
.withExistingValue(existing);
// Binding with annotations
Bindable<ServerConfig> withValidation = Bindable.of(ServerConfig.class)
.withAnnotations(new Validated() {
@Override
public Class<? extends Annotation> annotationType() {
return Validated.class;
}
});A container object to return the result of a bind operation.
package org.springframework.boot.context.properties.bind;
/**
* A container object to return the result of a Binder bind operation. May contain
* either a successfully bound object or an empty result.
*
* @param <T> the result type
* @since 2.0.0
*/
public final class BindResult<T> {
/**
* Return the object that was bound or throw a NoSuchElementException if no
* value was bound.
*
* @return the bound value (never null)
* @throws NoSuchElementException if no value was bound
*/
public T get() throws NoSuchElementException;
/**
* Returns true if a result was bound.
* @return if a result was bound
*/
public boolean isBound();
/**
* Invoke the specified consumer with the bound value, or do nothing if no
* value has been bound.
*
* @param consumer block to execute if a value has been bound
*/
public void ifBound(Consumer<? super T> consumer);
/**
* Apply the provided mapping function to the bound value, or return an
* updated unbound result if no value has been bound.
*
* @param <U> the type of the result of the mapping function
* @param mapper a mapping function to apply to the bound value
* @return a BindResult describing the result of applying a mapping function
* to the value of this BindResult
*/
public <U> BindResult<U> map(Function<? super T, ? extends U> mapper);
/**
* Return the object that was bound, or other if no value has been bound.
*
* @param other the value to be returned if there is no bound value
* (may be null)
* @return the value, if bound, otherwise other
*/
public T orElse(T other);
/**
* Return the object that was bound, or the result of invoking other if no
* value has been bound.
*
* @param other a Supplier of the value to be returned if there is no bound value
* @return the value, if bound, otherwise the supplied other
*/
public T orElseGet(Supplier<? extends T> other);
/**
* Return the object that was bound, or throw an exception to be created by
* the provided supplier if no value has been bound.
*
* @param <X> the type of the exception to be thrown
* @param exceptionSupplier the supplier which will return the exception to be thrown
* @return the present value
* @throws X if there is no value present
*/
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
throws X;
}Usage Example:
Binder binder = Binder.get(environment);
// Check if bound
BindResult<String> result = binder.bind("app.name", String.class);
if (result.isBound()) {
String name = result.get();
}
// Use ifBound
result.ifBound(name -> System.out.println("App name: " + name));
// Provide default value
String appName = result.orElse("default-app");
// Provide default via supplier
String appName2 = result.orElseGet(() -> "computed-default");
// Throw exception if not bound
String appName3 = result.orElseThrow(
() -> new IllegalStateException("app.name is required")
);
// Map the result
BindResult<Integer> length = result.map(String::length);Callback interface for handling logic during element binding.
package org.springframework.boot.context.properties.bind;
/**
* Callback interface that can be used to handle additional logic during element
* Binder binding.
*
* @since 2.0.0
*/
public interface BindHandler {
/**
* Default no-op bind handler.
*/
BindHandler DEFAULT = new BindHandler() {};
/**
* Called when binding of an element starts but before any result has been
* determined.
*
* @param <T> the bindable source type
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @return the actual item that should be used for binding (may be null)
*/
default <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target,
BindContext context) {
return target;
}
/**
* Called when binding of an element ends with a successful result.
* Implementations may change the ultimately returned result or perform
* addition validation.
*
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @param result the bound result (never null)
* @return the actual result that should be used (may be null)
*/
default Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) {
return result;
}
/**
* Called when binding of an element ends with an unbound result and a newly
* created instance is about to be returned. Implementations may change the
* ultimately returned result or perform addition validation.
*
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @param result the newly created instance (never null)
* @return the actual result that should be used (must not be null)
* @since 2.2.2
*/
default Object onCreate(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) {
return result;
}
/**
* Called when binding fails for any reason (including failures from
* onSuccess or onCreate calls). Implementations may choose to swallow
* exceptions and return an alternative result.
*
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @param error the cause of the error (if the exception stands it may be re-thrown)
* @return the actual result that should be used (may be null)
* @throws Exception if the binding isn't valid
*/
default Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Exception error) throws Exception {
throw error;
}
/**
* Called when binding finishes with either bound or unbound result. This
* method will not be called when binding failed, even if a handler returns
* a result from onFailure.
*
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @param result the bound result (may be null)
* @throws Exception if the binding isn't valid
*/
default void onFinish(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception {
}
}Usage Example:
// Custom BindHandler that logs binding operations
public class LoggingBindHandler implements BindHandler {
private static final Logger log = LoggerFactory.getLogger(LoggingBindHandler.class);
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target,
BindContext context) {
log.debug("Starting bind: {}", name);
return target;
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) {
log.debug("Bound successfully: {} = {}", name, result);
return result;
}
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Exception error) throws Exception {
log.error("Binding failed for: {}", name, error);
throw error;
}
}
// Use custom handler
Binder binder = Binder.get(environment);
BindHandler handler = new LoggingBindHandler();
BindResult<DatabaseConfig> result = binder.bind("app.database",
Bindable.of(DatabaseConfig.class), handler);Strategy for resolving property placeholders.
package org.springframework.boot.context.properties.bind;
/**
* Strategy interface used to resolve placeholder values.
*
* @since 2.0.0
*/
@FunctionalInterface
public interface PlaceholdersResolver {
/**
* No-op PropertyResolver.
*/
PlaceholdersResolver NONE = (value) -> value;
/**
* Resolve placeholders in the given value.
* @param value the value to resolve (may be {@code null})
* @return the resolved value (may be {@code null})
*/
@Nullable
Object resolvePlaceholders(Object value);
}Usage Example:
// Custom PlaceholdersResolver
PlaceholdersResolver resolver = value -> {
if (value instanceof String str) {
return str.replace("${home}", System.getProperty("user.home"));
}
return value;
};
// Use with Binder
Iterable<ConfigurationPropertySource> sources =
ConfigurationPropertySources.get(environment);
Binder binder = new Binder(sources, resolver);Strategy interface for determining which constructor to use for binding.
package org.springframework.boot.context.properties.bind;
/**
* Strategy interface used to determine a specific constructor to use when binding.
*
* @since 2.2.1
*/
@FunctionalInterface
public interface BindConstructorProvider {
/**
* Default BindConstructorProvider implementation that only returns a value
* when there's a single constructor and when the bindable has no existing value.
*/
BindConstructorProvider DEFAULT = new DefaultBindConstructorProvider();
/**
* Return the bind constructor to use for the given bindable, or null if
* constructor binding is not supported.
*
* @param bindable the bindable to check
* @param isNestedConstructorBinding if this binding is nested within a
* constructor binding
* @return the bind constructor or null
*/
Constructor<?> getBindConstructor(Bindable<?> bindable,
boolean isNestedConstructorBinding);
/**
* Return the bind constructor for the given type considering nested constructor binding.
* @param type the type to return
* @param isNestedConstructorBinding whether nested constructor binding is being used
* @return the bind constructor or {@code null}
* @since 3.0.0
*/
default Constructor<?> getBindConstructor(Class<?> type,
boolean isNestedConstructorBinding) {
return getBindConstructor(Bindable.of(type), isNestedConstructorBinding);
}
}Context information for use by BindHandler implementations.
package org.springframework.boot.context.properties.bind;
/**
* Context information for use by {@link BindHandler BindHandlers}.
*
* @since 2.0.0
*/
public interface BindContext {
/**
* Return the binder that is performing the current bind operation.
* @return the binder
*/
Binder getBinder();
/**
* Return the current depth of the binding. Root binding starts with a depth of {@code 0}. Each sub-binding increases the depth by {@code 1}.
* @return the depth of the current binding
*/
int getDepth();
/**
* Return the configuration property sources used by the binder.
* @return the sources
*/
Iterable<ConfigurationPropertySource> getSources();
/**
* Return the {@link ConfigurationProperty} being bound or {@code null} if the property has not yet been determined.
* @return the configuration property (may be {@code null})
*/
@Nullable
ConfigurationProperty getConfigurationProperty();
}Enum defining configuration property binding methods.
package org.springframework.boot.context.properties.bind;
/**
* Bind method that can be used when binding an object.
*
* @since 2.5.0
*/
public enum BindMethod {
/**
* Java Bean binding using getter/setter methods.
*/
JAVA_BEAN,
/**
* Value object binding using constructor parameters.
*/
VALUE_OBJECT
}Exception thrown when binding fails.
package org.springframework.boot.context.properties.bind;
/**
* Exception thrown when binding fails.
*
* @since 2.0.0
*/
public class BindException extends RuntimeException implements OriginProvider {
/**
* Return the name of the configuration property being bound.
* @return the property name
*/
public ConfigurationPropertyName getName();
/**
* Return the target being bound.
* @return the bind target
*/
public Bindable<?> getTarget();
/**
* Return the configuration property being bound.
* @return the configuration property (may be {@code null})
*/
@Nullable
public ConfigurationProperty getProperty();
/**
* Return the origin of the property or {@code null} if the origin is not known.
* @return the property origin
*/
@Nullable
public Origin getOrigin();
}Abstract base class for BindHandler implementations.
package org.springframework.boot.context.properties.bind;
/**
* Abstract base class for BindHandler implementations.
*
* @since 2.0.0
*/
public abstract class AbstractBindHandler implements BindHandler {
/**
* Create a new binding handler instance.
*/
public AbstractBindHandler();
/**
* Create a new binding handler instance with a specific parent.
*
* @param parent the parent handler
*/
public AbstractBindHandler(BindHandler parent);
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target,
BindContext context);
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result);
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Exception error) throws Exception;
@Override
public void onFinish(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception;
}Internal utility to help when dealing with data object property names.
package org.springframework.boot.context.properties.bind;
/**
* Internal utility to help when dealing with data object property names.
*
* @since 2.2.3
*/
public abstract class DataObjectPropertyName {
/**
* Return the specified Java Bean property name in dashed form.
*
* @param name the source name
* @return the dashed form
*/
public static String toDashedForm(String name);
}Usage Example:
// Convert camelCase to dashed-form
String dashed = DataObjectPropertyName.toDashedForm("maxConnections");
// Returns: "max-connections"
String dashed2 = DataObjectPropertyName.toDashedForm("databaseURL");
// Returns: "database-u-r-l"Annotation to specify default values when binding to immutable properties.
package org.springframework.boot.context.properties.bind;
/**
* Annotation that can be used to specify the default value when binding to an
* immutable property. This annotation can also be used with nested properties to
* indicate that a value should always be bound (rather than binding null).
* The value from this annotation will only be used if the property is not found
* in the property sources used by the Binder.
*
* NOTE: This annotation does not support property placeholder resolution and the
* value must be constant.
*
* @since 2.2.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER })
@Documented
public @interface DefaultValue {
/**
* The default value of the property. Can be an array of values for
* collection or array-based properties.
*
* @return the default value of the property
*/
String[] value() default {};
}Usage Example:
@ConfigurationProperties("app.service")
public class ServiceProperties {
private final String name;
private final int timeout;
private final List<String> endpoints;
@ConstructorBinding
public ServiceProperties(
String name,
@DefaultValue("30") int timeout,
@DefaultValue({"http://default1.example.com", "http://default2.example.com"})
List<String> endpoints) {
this.name = name;
this.timeout = timeout;
this.endpoints = endpoints;
}
// Getters
}Annotation to specify the name when binding to a property.
package org.springframework.boot.context.properties.bind;
/**
* Annotation that can be used to specify the name when binding to a property.
* This annotation may be required when binding to names that clash with reserved
* language keywords.
*
* When naming a JavaBean-based property, annotate the field. When naming a
* constructor-bound property, annotate the constructor parameter or record component.
*
* @since 2.4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Documented
public @interface Name {
/**
* The name of the property to use for binding.
*
* @return the property name
*/
String value();
}Usage Example:
@ConfigurationProperties("app.config")
public class AppConfig {
// Use @Name to avoid clash with Java keyword
@Name("class")
private String className;
public String getClassName() { return className; }
public void setClassName(String className) { this.className = className; }
}
// Constructor binding example
@ConfigurationProperties("app.server")
public record ServerConfig(
@Name("class") String serverClass,
int port
) {}Meta-annotation indicating a field is a nested type.
package org.springframework.boot.context.properties.bind;
/**
* Meta-annotation that should be added to annotations that indicate a field is
* a nested type. Used to ensure that correct reflection hints are registered.
*
* @since 3.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@Documented
public @interface Nested {
}BindHandler to apply validators to bound results.
package org.springframework.boot.context.properties.bind.validation;
/**
* BindHandler to apply Validators to bound results.
*
* @since 2.0.0
*/
public class ValidationBindHandler extends AbstractBindHandler {
/**
* Create a new ValidationBindHandler instance.
*
* @param validators the validators to apply
*/
public ValidationBindHandler(Validator... validators);
/**
* Create a new ValidationBindHandler instance.
*
* @param parent the parent handler
* @param validators the validators to apply
*/
public ValidationBindHandler(BindHandler parent, Validator... validators);
}Error thrown when validation fails during a bind operation.
package org.springframework.boot.context.properties.bind.validation;
/**
* Error thrown when validation fails during a bind operation.
*
* @since 2.0.0
*/
public class BindValidationException extends RuntimeException {
/**
* Return the validation errors that caused the exception.
*
* @return the validation errors
*/
public ValidationErrors getValidationErrors();
}A collection of ObjectErrors caused by bind validation failures.
package org.springframework.boot.context.properties.bind.validation;
/**
* A collection of ObjectErrors caused by bind validation failures.
* Where possible, included FieldErrors will be OriginProvider.
*
* @since 2.0.0
*/
public class ValidationErrors implements Iterable<ObjectError> {
/**
* Return the name of the item that was being validated.
*
* @return the name of the item
*/
public ConfigurationPropertyName getName();
/**
* Return the properties that were bound before validation failed.
*
* @return the bound properties
*/
public Set<ConfigurationProperty> getBoundProperties();
/**
* Return true if there are validation errors.
*
* @return true if there are errors
*/
public boolean hasErrors();
/**
* Return the list of all validation errors.
*
* @return the errors
*/
public List<ObjectError> getAllErrors();
@Override
public Iterator<ObjectError> iterator();
}Usage Example:
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Min;
import jakarta.validation.Validator;
import jakarta.validation.Validation;
@ConfigurationProperties("app.database")
@Validated
public class DatabaseProperties {
@NotNull
private String url;
@Min(1)
private int maxConnections = 10;
// Getters and setters
}
// Using ValidationBindHandler programmatically
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
BindHandler handler = new ValidationBindHandler(validator);
try {
BindResult<DatabaseProperties> result = binder.bind("app.database",
Bindable.of(DatabaseProperties.class), handler);
} catch (BindValidationException ex) {
ValidationErrors errors = ex.getValidationErrors();
System.out.println("Validation failed for: " + errors.getName());
errors.getAllErrors().forEach(error ->
System.out.println(" - " + error.getDefaultMessage())
);
}Spring Boot provides several built-in BindHandler implementations for common binding scenarios.
package org.springframework.boot.context.properties.bind.handler;
/**
* BindHandler that can be used to ignore binding errors.
*
* @since 2.0.0
*/
public class IgnoreErrorsBindHandler extends AbstractBindHandler {
public IgnoreErrorsBindHandler();
public IgnoreErrorsBindHandler(BindHandler parent);
}Usage Example:
Binder binder = Binder.get(environment);
BindHandler handler = new IgnoreErrorsBindHandler();
// Binding will not fail even if there are type conversion errors
BindResult<MyConfig> result = binder.bind("app.config",
Bindable.of(MyConfig.class), handler);package org.springframework.boot.context.properties.bind.handler;
/**
* BindHandler that can be used to ignore ConversionFailedException for the
* top level, but not for nested elements.
*
* @since 2.0.0
*/
public class IgnoreTopLevelConverterNotFoundBindHandler extends AbstractBindHandler {
public IgnoreTopLevelConverterNotFoundBindHandler();
public IgnoreTopLevelConverterNotFoundBindHandler(BindHandler parent);
}package org.springframework.boot.context.properties.bind.handler;
/**
* BindHandler that can be used to enforce that all configuration properties
* under the root name have been consumed.
*
* @since 2.0.0
*/
public class NoUnboundElementsBindHandler extends AbstractBindHandler {
public NoUnboundElementsBindHandler();
public NoUnboundElementsBindHandler(BindHandler parent);
}Usage Example:
// Fail if there are properties that weren't bound
BindHandler handler = new NoUnboundElementsBindHandler();
binder.bind("app.database", Bindable.of(DatabaseConfig.class), handler);
// Throws BindException if app.database.unknown-property existspackage org.springframework.boot.context.properties.bind.validation;
/**
* BindHandler to apply Validators to bound results.
*
* @since 2.0.0
*/
public class ValidationBindHandler extends AbstractBindHandler {
/**
* Create a new ValidationBindHandler instance.
*
* @param validators the validators to apply
*/
public ValidationBindHandler(Validator... validators);
/**
* Create a new ValidationBindHandler instance.
*
* @param parent the parent handler
* @param validators the validators to apply
*/
public ValidationBindHandler(BindHandler parent, Validator... validators);
}Usage Example:
import jakarta.validation.Validator;
import jakarta.validation.Validation;
// Create validator
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
// Use validation handler
BindHandler handler = new ValidationBindHandler(validator);
BindResult<MyConfig> result = binder.bind("app.config",
Bindable.of(MyConfig.class), handler);
// Throws BindValidationException if validation failsProvides access to @ConfigurationProperties bean details.
package org.springframework.boot.context.properties;
/**
* Provides access to @ConfigurationProperties bean details, regardless of if the
* annotation was used directly or on a @Bean factory method. This class can be
* used to access all configuration properties beans in an ApplicationContext, or
* individual beans on a case-by-case basis.
*
* @since 2.2.0
*/
public final class ConfigurationPropertiesBean {
/**
* Return the name of the Spring bean.
* @return the bean name
*/
public String getName();
/**
* Return the actual Spring bean instance.
* @return the bean instance
*/
public Object getInstance();
/**
* Return the ConfigurationProperties annotation for the bean. The annotation
* may be defined on the bean itself or from the factory method that create
* the bean (usually a @Bean method).
*
* @return the configuration properties annotation
*/
public ConfigurationProperties getAnnotation();
/**
* Return a Bindable instance suitable that can be used as a target for the
* Binder.
*
* @return a bind target for use with the Binder
*/
public Bindable<?> asBindTarget();
/**
* Return all @ConfigurationProperties beans contained in the given
* application context. Both directly annotated beans, as well as beans
* that have @ConfigurationProperties annotated factory methods are included.
*
* @param applicationContext the source application context
* @return a map of all configuration properties beans keyed by the bean name
*/
public static Map<String, ConfigurationPropertiesBean> getAll(
ApplicationContext applicationContext);
/**
* Return a ConfigurationPropertiesBean instance for the given bean details
* or null if the bean is not a @ConfigurationProperties object. Annotations
* are considered both on the bean itself, as well as any factory method.
*
* @param applicationContext the source application context
* @param bean the bean to consider
* @param beanName the bean name
* @return a configuration properties bean or null
*/
public static ConfigurationPropertiesBean get(ApplicationContext applicationContext,
Object bean, String beanName);
/**
* Create a ConfigurationPropertiesBean for a value object.
* Note: This method is package-private and intended for internal use.
*
* @param beanType the bean type
* @param beanName the bean name
* @return a configuration properties bean
*/
static ConfigurationPropertiesBean forValueObject(Class<?> beanType, String beanName);
}Usage Example:
@Component
public class ConfigurationPropertiesReporter {
private final ApplicationContext context;
public ConfigurationPropertiesReporter(ApplicationContext context) {
this.context = context;
}
public void reportAllProperties() {
Map<String, ConfigurationPropertiesBean> beans =
ConfigurationPropertiesBean.getAll(context);
beans.forEach((name, bean) -> {
ConfigurationProperties annotation = bean.getAnnotation();
System.out.println("Bean: " + name);
System.out.println(" Prefix: " + annotation.prefix());
System.out.println(" Type: " + bean.getInstance().getClass().getName());
});
}
}Helper class for programmatically binding configuration properties that use constructor injection.
package org.springframework.boot.context.properties;
import org.springframework.beans.factory.BeanFactory;
/**
* Helper class to programmatically bind configuration properties that use constructor
* injection.
*
* @since 3.0.0
* @see ConstructorBinding
*/
public abstract class ConstructorBound {
/**
* Create an immutable @ConfigurationProperties instance for the specified
* beanName and beanType using the specified BeanFactory.
*
* @param beanFactory the bean factory to use
* @param beanName the name of the bean
* @param beanType the type of the bean
* @return an instance from the specified bean
* @throws ConfigurationPropertiesBindException if binding fails
*/
public static Object from(BeanFactory beanFactory, String beanName, Class<?> beanType);
}Usage Example:
Use this helper to programmatically create immutable configuration properties objects:
import org.springframework.boot.context.properties.ConstructorBound;
import org.springframework.beans.factory.BeanFactory;
@Configuration
public class AppConfiguration {
private final BeanFactory beanFactory;
public AppConfiguration(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Bean
public MyImmutableProperties myProperties() {
// Create immutable properties instance using constructor binding
return (MyImmutableProperties) ConstructorBound.from(
beanFactory,
"myProperties",
MyImmutableProperties.class
);
}
}
// Immutable properties class with constructor binding
@ConfigurationProperties(prefix = "app")
public record MyImmutableProperties(
String name,
int port,
Duration timeout
) {
// Constructor binding happens automatically with records
}SPI (Service Provider Interface) for customizing the BindHandler used during @ConfigurationProperties binding. Implementations can add additional validation, transformation, or logging to the binding process.
package org.springframework.boot.context.properties;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
/**
* Allows additional functionality to be applied to the BindHandler used by the
* ConfigurationPropertiesBindingPostProcessor.
*
* Implementations are typically registered as beans in the application context and
* are automatically detected and applied during configuration properties binding.
*
* @since 2.1.0
* @see AbstractBindHandler
*/
@FunctionalInterface
public interface ConfigurationPropertiesBindHandlerAdvisor {
/**
* Apply additional functionality to the source bind handler.
*
* @param bindHandler the source bind handler
* @return a replacement bind handler that delegates to the source and provides
* additional functionality
*/
BindHandler apply(BindHandler bindHandler);
}ConfigurationPropertiesBindingPostProcessorpackage com.example.config;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindHandlerAdvisor;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.stereotype.Component;
/**
* Custom bind handler advisor that logs all configuration properties binding.
*/
@Component
public class LoggingBindHandlerAdvisor implements ConfigurationPropertiesBindHandlerAdvisor {
@Override
public BindHandler apply(BindHandler bindHandler) {
return new LoggingBindHandler(bindHandler);
}
/**
* BindHandler that logs binding operations.
*/
private static class LoggingBindHandler extends AbstractBindHandler {
LoggingBindHandler(BindHandler parent) {
super(parent);
}
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target,
BindContext context) {
System.out.println("Binding property: " + name);
return super.onStart(name, target, context);
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) {
System.out.println("Successfully bound property: " + name + " = " + result);
return super.onSuccess(name, target, context, result);
}
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Exception error) throws Exception {
System.err.println("Failed to bind property: " + name + " - " + error.getMessage());
return super.onFailure(name, target, context, error);
}
}
}package com.example.config;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindHandlerAdvisor;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* Advisor that enforces custom validation rules during binding.
*/
@Component
@Order(1) // Controls order when multiple advisors are present
public class ValidationBindHandlerAdvisor implements ConfigurationPropertiesBindHandlerAdvisor {
@Override
public BindHandler apply(BindHandler bindHandler) {
return new ValidationBindHandler(bindHandler);
}
private static class ValidationBindHandler extends AbstractBindHandler {
ValidationBindHandler(BindHandler parent) {
super(parent);
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception {
// Custom validation after successful binding
validateResult(name, result);
return super.onSuccess(name, target, context, result);
}
private void validateResult(ConfigurationPropertyName name, Object result) {
// Example: Validate port numbers
if (name.toString().endsWith(".port") && result instanceof Integer port) {
if (port < 1024 || port > 65535) {
throw new IllegalArgumentException(
"Port " + name + " must be between 1024 and 65535, got: " + port);
}
}
// Example: Validate URLs
if (name.toString().endsWith(".url") && result instanceof String url) {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
throw new IllegalArgumentException(
"URL " + name + " must start with http:// or https://, got: " + url);
}
}
// Example: Validate sensitive properties are not empty
if (name.toString().contains("password") && result instanceof String pwd) {
if (pwd.isEmpty()) {
throw new IllegalArgumentException(
"Password property " + name + " cannot be empty");
}
}
}
}
}package com.example.config;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindHandlerAdvisor;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.stereotype.Component;
/**
* Advisor that transforms property values during binding.
*/
@Component
public class TransformationBindHandlerAdvisor
implements ConfigurationPropertiesBindHandlerAdvisor {
@Override
public BindHandler apply(BindHandler bindHandler) {
return new TransformationBindHandler(bindHandler);
}
private static class TransformationBindHandler extends AbstractBindHandler {
TransformationBindHandler(BindHandler parent) {
super(parent);
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception {
// Transform the result before passing to parent
Object transformed = transformValue(name, result);
return super.onSuccess(name, target, context, transformed);
}
private Object transformValue(ConfigurationPropertyName name, Object value) {
// Example: Normalize all string values to lowercase for specific properties
if (name.toString().startsWith("app.environment") && value instanceof String) {
return ((String) value).toLowerCase();
}
// Example: Trim whitespace from all string values
if (value instanceof String) {
return ((String) value).trim();
}
// Example: Convert negative durations to positive
if (value instanceof java.time.Duration duration) {
return duration.isNegative() ? duration.abs() : duration;
}
return value;
}
}
}package com.example.config;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindHandlerAdvisor;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
/**
* Advisor that maintains an audit trail of all bound properties.
*/
@Component
public class AuditBindHandlerAdvisor implements ConfigurationPropertiesBindHandlerAdvisor {
private final List<AuditEntry> auditLog = new ArrayList<>();
@Override
public BindHandler apply(BindHandler bindHandler) {
return new AuditBindHandler(bindHandler, auditLog);
}
public List<AuditEntry> getAuditLog() {
return new ArrayList<>(auditLog);
}
private static class AuditBindHandler extends AbstractBindHandler {
private final List<AuditEntry> auditLog;
AuditBindHandler(BindHandler parent, List<AuditEntry> auditLog) {
super(parent);
this.auditLog = auditLog;
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception {
// Record successful binding
auditLog.add(new AuditEntry(
name.toString(),
result != null ? result.getClass().getSimpleName() : "null",
maskSensitiveValue(name, result),
Instant.now(),
true,
null
));
return super.onSuccess(name, target, context, result);
}
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Exception error) throws Exception {
// Record failed binding
auditLog.add(new AuditEntry(
name.toString(),
target.getType().toString(),
null,
Instant.now(),
false,
error.getMessage()
));
return super.onFailure(name, target, context, error);
}
private String maskSensitiveValue(ConfigurationPropertyName name, Object value) {
String nameStr = name.toString();
// Mask sensitive properties
if (nameStr.contains("password") || nameStr.contains("secret") ||
nameStr.contains("token") || nameStr.contains("key")) {
return "***MASKED***";
}
return value != null ? value.toString() : "null";
}
}
public record AuditEntry(
String propertyName,
String type,
String value,
Instant timestamp,
boolean success,
String errorMessage
) {}
}When registering multiple advisors, use @Order to control their execution sequence:
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(1) // Runs first
public class ValidationBindHandlerAdvisor
implements ConfigurationPropertiesBindHandlerAdvisor {
// Validation logic
}
@Component
@Order(2) // Runs second
public class TransformationBindHandlerAdvisor
implements ConfigurationPropertiesBindHandlerAdvisor {
// Transformation logic
}
@Component
@Order(3) // Runs third
public class LoggingBindHandlerAdvisor
implements ConfigurationPropertiesBindHandlerAdvisor {
// Logging logic
}The advisors are applied in order, creating a chain:
Original Handler -> Validation -> Transformation -> Logging -> Actual BindingFor simple use cases, you can define advisors as lambda expressions:
import org.springframework.boot.context.properties.ConfigurationPropertiesBindHandlerAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BindHandlerConfiguration {
@Bean
public ConfigurationPropertiesBindHandlerAdvisor simpleLoggingAdvisor() {
return bindHandler -> new AbstractBindHandler(bindHandler) {
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name,
Bindable<T> target,
BindContext context) {
System.out.println("Binding: " + name);
return super.onStart(name, target, context);
}
};
}
}AbstractBindHandler provides a convenient base class for custom bind handlers:
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
public class CustomBindHandler extends AbstractBindHandler {
public CustomBindHandler(BindHandler parent) {
super(parent);
}
// Override any of these lifecycle methods:
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target,
BindContext context) {
// Called when binding starts for a property
return super.onStart(name, target, context);
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception {
// Called when binding succeeds
return super.onSuccess(name, target, context, result);
}
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Exception error) throws Exception {
// Called when binding fails
return super.onFailure(name, target, context, error);
}
@Override
public void onFinish(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) throws Exception {
// Called when binding finishes (success or failure)
super.onFinish(name, target, context, result);
}
}super methods in AbstractBindHandler to maintain the handler chain@Order when using multiple advisors to control execution sequenceBeanPostProcessor that binds @ConfigurationProperties annotated beans to external configuration from property sources.
package org.springframework.boot.context.properties;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.PriorityOrdered;
/**
* BeanPostProcessor to bind PropertySources to beans annotated with
* @ConfigurationProperties.
*
* @since 1.0.0
*/
public class ConfigurationPropertiesBindingPostProcessor
implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {
/**
* The bean name that this post-processor is registered with.
*/
public static final String BEAN_NAME =
"org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor";
/**
* Register a ConfigurationPropertiesBindingPostProcessor bean if one is not
* already registered.
*
* @param registry the bean definition registry
* @since 2.2.0
*/
public static void register(BeanDefinitionRegistry registry);
}Usage Example:
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CustomRegistrarConfiguration {
// Manually register the post-processor if needed
public void registerPostProcessor(BeanDefinitionRegistry registry) {
ConfigurationPropertiesBindingPostProcessor.register(registry);
}
}Internal class for binding @ConfigurationProperties beans.
package org.springframework.boot.context.properties;
/**
* Internal class used by the ConfigurationPropertiesBindingPostProcessor to
* handle the actual @ConfigurationProperties binding.
*
* @since 2.2.0
*/
class ConfigurationPropertiesBinder {
/**
* Bind the given configuration properties bean.
*
* @param propertiesBean the properties bean to bind
* @return the binding result
*/
BindResult<?> bind(ConfigurationPropertiesBean propertiesBean);
/**
* Bind the given configuration properties bean or create a new instance if
* the result is null.
*
* @param propertiesBean the properties bean to bind
* @return the bound or created object
*/
Object bindOrCreate(ConfigurationPropertiesBean propertiesBean);
/**
* Get the ConfigurationPropertiesBinder from the given BeanFactory.
*
* @param beanFactory the bean factory
* @return the ConfigurationPropertiesBinder
*/
static ConfigurationPropertiesBinder get(BeanFactory beanFactory);
}Bean to record and provide bound @ConfigurationProperties.
package org.springframework.boot.context.properties;
/**
* Bean to record and provide bound @ConfigurationProperties.
*
* @since 2.3.0
*/
public class BoundConfigurationProperties {
/**
* Get the configuration property bound to the given name.
*
* @param name the property name
* @return the bound property or null
*/
public ConfigurationProperty get(ConfigurationPropertyName name);
/**
* Get all bound properties.
*
* @return a map of all bound properties
*/
public Map<ConfigurationPropertyName, ConfigurationProperty> getAll();
/**
* Return the BoundConfigurationProperties from the given ApplicationContext
* if it is available.
*
* @param context the context to search
* @return a BoundConfigurationProperties or null
*/
public static BoundConfigurationProperties get(ApplicationContext context);
}Usage Example:
@Component
public class BoundPropertiesReporter {
private final ApplicationContext context;
public BoundPropertiesReporter(ApplicationContext context) {
this.context = context;
}
public void reportBoundProperties() {
BoundConfigurationProperties bound = BoundConfigurationProperties.get(context);
if (bound != null) {
Map<ConfigurationPropertyName, ConfigurationProperty> all = bound.getAll();
all.forEach((name, property) -> {
System.out.println(name + " = " + property.getValue());
});
}
}
}Utility for mapping values from a source to a destination.
package org.springframework.boot.context.properties;
/**
* Utility that can be used to map values from a supplied source to a destination.
* Primarily intended to be help when mapping from @ConfigurationProperties to
* third-party classes.
*
* Can filter values based on predicates and adapt values if needed. For example:
* PropertyMapper map = PropertyMapper.get();
* map.from(source::getName)
* .to(destination::setName);
* map.from(source::getTimeout)
* .when(this::thisYear)
* .asInt(Duration::getSeconds)
* .to(destination::setTimeoutSecs);
* map.from(source::isEnabled)
* .whenFalse()
* .toCall(destination::disable);
*
* By default null values and any NullPointerException thrown from the supplier
* are filtered and will not be applied to consumers. If you want to apply nulls,
* you can use Source.always().
*
* @since 2.0.0
*/
public final class PropertyMapper {
/**
* Return a new PropertyMapper instance that applies the given SourceOperator
* to every source.
*
* @param operator the source operator to apply
* @return a new property mapper instance
*/
public PropertyMapper alwaysApplying(SourceOperator operator);
/**
* Return a new Source from the specified value that can be used to perform
* the mapping.
*
* @param <T> the source type
* @param value the value
* @return a Source that can be used to complete the mapping
*/
public <T> Source<T> from(T value);
/**
* Return a new Source from the specified value supplier that can be used to
* perform the mapping.
*
* @param <T> the source type
* @param supplier the value supplier
* @return a Source that can be used to complete the mapping
*/
public <T> Source<T> from(Supplier<? extends T> supplier);
/**
* Return the property mapper.
* @return the property mapper
*/
public static PropertyMapper get();
/**
* An operation that can be applied to a Source.
*/
@FunctionalInterface
public interface SourceOperator {
/**
* Apply the operation to the given source.
*
* @param <T> the source type
* @param source the source to operate on
* @return the updated source
*/
<T> Source<T> apply(Source<T> source);
}
/**
* A source that is in the process of being mapped.
*
* @param <T> the source type
*/
public static final class Source<T> {
/**
* Return a source that will use the given supplier to obtain a fallback
* value to use in place of null.
*
* @param fallback the fallback supplier
* @return a new Source instance
* @since 4.0.0
*/
public Source<T> orFrom(Supplier<? extends T> fallback);
/**
* Return an adapted version of the source with Integer type.
*
* @param <R> the resulting type
* @param adapter an adapter to convert the current value to a number
* @return a new adapted source instance
*/
public <R extends Number> Source<Integer> asInt(Adapter<? super T, ? extends R> adapter);
/**
* Return an adapted version of the source changed through the given
* adapter function.
*
* @param <R> the resulting type
* @param adapter the adapter to apply
* @return a new adapted source instance
*/
public <R> Source<R> as(Adapter<? super T, ? extends R> adapter);
/**
* Return a filtered version of the source that will only map values
* that are true.
*
* @return a new filtered source instance
*/
public Source<T> whenTrue();
/**
* Return a filtered version of the source that will only map values
* that are false.
*
* @return a new filtered source instance
*/
public Source<T> whenFalse();
/**
* Return a filtered version of the source that will only map values
* that have a toString() containing actual text.
*
* @return a new filtered source instance
*/
public Source<T> whenHasText();
/**
* Return a filtered version of the source that will only map values
* equal to the specified object.
*
* @param object the object to match
* @return a new filtered source instance
*/
public Source<T> whenEqualTo(Object object);
/**
* Return a filtered version of the source that will only map values
* that are an instance of the given type.
*
* @param <R> the target type
* @param target the target type to match
* @return a new filtered source instance
*/
public <R extends T> Source<R> whenInstanceOf(Class<R> target);
/**
* Return a filtered version of the source that won't map values that
* match the given predicate.
*
* @param predicate the predicate used to filter values
* @return a new filtered source instance
*/
public Source<T> whenNot(Predicate<T> predicate);
/**
* Return a filtered version of the source that won't map values that
* don't match the given predicate.
*
* @param predicate the predicate used to filter values
* @return a new filtered source instance
*/
public Source<T> when(Predicate<T> predicate);
/**
* Complete the mapping by passing any non-filtered value to the
* specified consumer. The method is designed to be used with mutable objects.
*
* @param consumer the consumer that should accept the value if it's
* not been filtered
*/
public void to(Consumer<? super T> consumer);
/**
* Complete the mapping for any non-filtered value by applying the given
* function to an existing instance and returning a new one. For filtered
* values, the instance parameter is returned unchanged.
*
* @param <R> the result type
* @param instance the current instance
* @param mapper the mapping function
* @return a new mapped instance or the original instance
* @since 3.0.0
*/
public <R> R to(R instance, BiFunction<R, ? super T, R> mapper);
/**
* Complete the mapping by creating a new instance from the non-filtered value.
*
* @param <R> the resulting type
* @param factory the factory used to create the instance
* @return the instance
* @throws NoSuchElementException if the value has been filtered
*/
public <R> R toInstance(Function<? super T, R> factory);
/**
* Complete the mapping by calling the specified method when the value
* has not been filtered.
*
* @param runnable the method to call if the value has not been filtered
*/
public void toCall(Runnable runnable);
/**
* Return a version of this source that can be used to always complete
* mappings, even if values are null.
*
* @return a new Always instance
* @since 4.0.0
*/
public Always<T> always();
/**
* Adapter used to adapt a value and possibly return a null result.
*
* @param <T> the source type
* @param <R> the result type
* @since 4.0.0
*/
@FunctionalInterface
public interface Adapter<T, R> {
/**
* Adapt the given value.
* @param value the value to adapt
* @return an adapted value or null
*/
R adapt(T value);
}
/**
* Allow source mapping to complete using methods that accept nulls.
*
* @param <T> the source type
* @since 4.0.0
*/
public static class Always<T> {
/**
* Return an adapted version of the source changed through the given
* adapter function.
*
* @param <R> the resulting type
* @param adapter the adapter to apply
* @return a new adapted source instance
*/
public <R> Always<R> as(Adapter<? super T, ? extends R> adapter);
/**
* Complete the mapping by passing any non-filtered value to the
* specified consumer. The method is designed to be used with mutable objects.
*
* @param consumer the consumer that should accept the value if it's
* not been filtered
*/
public void to(Consumer<? super T> consumer);
/**
* Complete the mapping for any non-filtered value by applying the given
* function to an existing instance and returning a new one. For filtered
* values, the instance parameter is returned unchanged.
*
* @param <R> the result type
* @param instance the current instance
* @param mapper the mapping function
* @return a new mapped instance or the original instance
*/
public <R> R to(R instance, Mapper<R, ? super T> mapper);
/**
* Complete the mapping by creating a new instance from the non-filtered value.
*
* @param <R> the resulting type
* @param factory the factory used to create the instance
* @return the instance
* @throws NoSuchElementException if the value has been filtered
*/
public <R> R toInstance(Factory<? super T, ? extends R> factory);
/**
* Complete the mapping by calling the specified method when the value
* has not been filtered.
*
* @param runnable the method to call if the value has not been filtered
*/
public void toCall(Runnable runnable);
/**
* Adapter that support nullable values.
*
* @param <T> the source type
* @param <R> the result type
*/
@FunctionalInterface
public interface Adapter<T, R> {
/**
* Adapt the given value.
* @param value the value to adapt
* @return an adapted value or null
*/
R adapt(T value);
}
/**
* Factory that supports nullable values.
*
* @param <T> the source type
* @param <R> the result type
*/
@FunctionalInterface
public interface Factory<T, R> {
/**
* Create a new instance for the given nullable value.
* @param value the value used to create the instance (may be null)
* @return the resulting instance
*/
R create(T value);
}
/**
* Mapper that supports nullable values.
*
* @param <R> the result type
* @param <T> the source type
*/
@FunctionalInterface
public interface Mapper<R, T> {
/**
* Map a existing instance for the given nullable value.
* @param instance the existing instance
* @param value the value to map (may be null)
* @return the resulting mapped instance
*/
R map(R instance, T value);
}
}
}
}Usage Example:
@Component
public class ServerConfigurer {
private final ServerProperties serverProperties;
private final Server server;
public ServerConfigurer(ServerProperties serverProperties, Server server) {
this.serverProperties = serverProperties;
this.server = server;
}
public void configure() {
PropertyMapper map = PropertyMapper.get();
// Simple mapping
map.from(serverProperties::getPort)
.to(server::setPort);
// Conditional mapping
map.from(serverProperties::getAddress)
.whenHasText()
.to(server::setAddress);
// Type conversion mapping
map.from(serverProperties::getTimeout)
.asInt(Duration::getSeconds)
.to(server::setTimeoutSeconds);
// Boolean conditional mapping
map.from(serverProperties::isCompressionEnabled)
.whenTrue()
.toCall(server::enableCompression);
// Custom adapter
map.from(serverProperties::getMaxConnections)
.when(n -> n > 0)
.as(n -> new ConnectionPool(n))
.to(server::setConnectionPool);
// With fallback
map.from(serverProperties::getHost)
.orFrom(() -> "localhost")
.to(server::setHost);
}
}
@ConfigurationProperties("app.server")
public class ServerProperties {
private int port = 8080;
private String address;
private Duration timeout = Duration.ofSeconds(30);
private boolean compressionEnabled;
private int maxConnections = 100;
private String host;
// Getters and setters
}Utility class for working with ConfigurationPropertySource instances.
package org.springframework.boot.context.properties.source;
/**
* Provides access to ConfigurationPropertySources.
*
* @since 2.0.0
*/
public final class ConfigurationPropertySources {
/**
* Attach a ConfigurationPropertySource support to the specified Environment.
* Adapts each PropertySource managed by the environment to a
* ConfigurationPropertySource and allows classic PropertySourcesPropertyResolver
* calls to resolve using configuration property names.
*
* The attached resolver will dynamically track any additions or removals from
* the underlying Environment property sources.
*
* @param environment the source environment (must be an instance of
* ConfigurableEnvironment)
*/
public static void attach(Environment environment);
/**
* Return a set of ConfigurationPropertySource instances that have previously
* been attached to the Environment.
*
* @param environment the source environment (must be an instance of
* ConfigurableEnvironment)
* @return an iterable set of configuration property sources
* @throws IllegalStateException if no configuration property sources have been
* attached
*/
public static Iterable<ConfigurationPropertySource> get(Environment environment);
/**
* Return Iterable containing a single new ConfigurationPropertySource adapted
* from the given Spring PropertySource.
*
* @param source the Spring property source to adapt
* @return an Iterable containing a single newly adapted
* SpringConfigurationPropertySource
*/
public static Iterable<ConfigurationPropertySource> from(PropertySource<?> source);
/**
* Return Iterable containing new ConfigurationPropertySource instances adapted
* from the given Spring PropertySources.
*
* @param sources the Spring property sources to adapt
* @return an Iterable of newly adapted SpringConfigurationPropertySource
* instances
*/
public static Iterable<ConfigurationPropertySource> from(Iterable<PropertySource<?>> sources);
/**
* Create a new PropertyResolver that resolves property values against an
* underlying set of PropertySources.
*
* @param propertySources the set of PropertySource objects to use
* @return a ConfigurablePropertyResolver implementation
* @since 2.5.0
*/
public static ConfigurablePropertyResolver createPropertyResolver(
MutablePropertySources propertySources);
/**
* Determines if the specific PropertySource is the ConfigurationPropertySource
* that was attached to the Environment.
*
* @param propertySource the property source to test
* @return true if this is the attached ConfigurationPropertySource
*/
public static boolean isAttachedConfigurationPropertySource(PropertySource<?> propertySource);
}Usage Example:
// Attach configuration property sources to environment
ConfigurableEnvironment environment = new StandardEnvironment();
ConfigurationPropertySources.attach(environment);
// Get attached configuration property sources
Iterable<ConfigurationPropertySource> sources =
ConfigurationPropertySources.get(environment);
// Create a binder using the sources
Binder binder = new Binder(sources);
// Or use the simplified approach
Binder binder2 = Binder.get(environment);Here's a comprehensive example that demonstrates the various features:
// 1. JavaBean-style Configuration Properties
@ConfigurationProperties(prefix = "app.database")
@Validated
public class DatabaseProperties {
@NotNull
private String url;
@NotNull
private String username;
private String password;
@Min(1)
@Max(100)
private int maxConnections = 10;
@NestedConfigurationProperty
private PoolSettings pool = new PoolSettings();
// Getters and setters
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getMaxConnections() { return maxConnections; }
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
public PoolSettings getPool() { return pool; }
public void setPool(PoolSettings pool) { this.pool = pool; }
public static class PoolSettings {
private int minIdle = 5;
private int maxIdle = 10;
private Duration maxWait = Duration.ofSeconds(5);
// Getters and setters
public int getMinIdle() { return minIdle; }
public void setMinIdle(int minIdle) { this.minIdle = minIdle; }
public int getMaxIdle() { return maxIdle; }
public void setMaxIdle(int maxIdle) { this.maxIdle = maxIdle; }
public Duration getMaxWait() { return maxWait; }
public void setMaxWait(Duration maxWait) { this.maxWait = maxWait; }
}
}
// 2. Constructor Binding with Record
@ConfigurationProperties(prefix = "app.service")
public record ServiceProperties(
String name,
@DefaultValue("30") int timeout,
boolean enabled,
List<String> endpoints
) {}
// 3. Constructor Binding with Class
@ConfigurationProperties(prefix = "app.cache")
public class CacheProperties {
private final String type;
private final Duration ttl;
private final Map<String, String> config;
@ConstructorBinding
public CacheProperties(
String type,
@DefaultValue("1h") Duration ttl,
Map<String, String> config) {
this.type = type;
this.ttl = ttl;
this.config = config;
}
public String getType() { return type; }
public Duration getTtl() { return ttl; }
public Map<String, String> getConfig() { return config; }
}
// 4. Configuration with Deprecated Properties
@ConfigurationProperties(prefix = "app.legacy")
public class LegacyProperties {
private String oldEndpoint;
private String newEndpoint;
@DeprecatedConfigurationProperty(
replacement = "app.legacy.new-endpoint",
reason = "Use new-endpoint for improved security",
since = "2.0.0"
)
public String getOldEndpoint() { return oldEndpoint; }
public void setOldEndpoint(String oldEndpoint) { this.oldEndpoint = oldEndpoint; }
public String getNewEndpoint() { return newEndpoint; }
public void setNewEndpoint(String newEndpoint) { this.newEndpoint = newEndpoint; }
}
// 5. Enable Configuration Properties
@Configuration
@EnableConfigurationProperties({
DatabaseProperties.class,
ServiceProperties.class,
CacheProperties.class,
LegacyProperties.class
})
public class AppConfiguration {
// Use DatabaseProperties in a bean
@Bean
public DataSource dataSource(DatabaseProperties properties) {
HikariConfig config = new HikariConfig();
// Use PropertyMapper for clean mapping
PropertyMapper map = PropertyMapper.get();
map.from(properties::getUrl).to(config::setJdbcUrl);
map.from(properties::getUsername).to(config::setUsername);
map.from(properties::getPassword).whenHasText().to(config::setPassword);
map.from(properties::getMaxConnections).to(config::setMaximumPoolSize);
DatabaseProperties.PoolSettings pool = properties.getPool();
map.from(pool::getMinIdle).to(config::setMinimumIdle);
map.from(pool::getMaxIdle).when(n -> n > 0).to(config::setMaximumPoolSize);
map.from(pool::getMaxWait).asInt(Duration::toMillis).to(config::setConnectionTimeout);
return new HikariDataSource(config);
}
// Use Binder directly
@Bean
public CustomService customService(Environment environment) {
Binder binder = Binder.get(environment);
// Bind simple properties
String appName = binder.bind("app.name", String.class)
.orElse("default-app");
// Bind collections
List<String> servers = binder.bind("app.servers",
Bindable.listOf(String.class))
.orElseGet(ArrayList::new);
// Bind maps
Map<String, Integer> limits = binder.bind("app.limits",
Bindable.mapOf(String.class, Integer.class))
.orElseGet(HashMap::new);
return new CustomService(appName, servers, limits);
}
}
// 6. Programmatic Binding Example
@Component
public class DynamicConfigLoader {
private final Environment environment;
public DynamicConfigLoader(Environment environment) {
this.environment = environment;
}
public <T> T loadConfig(String prefix, Class<T> configClass) {
Binder binder = Binder.get(environment);
// Create custom bind handler for logging
BindHandler handler = new BindHandler() {
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Object result) {
System.out.println("Successfully bound: " + name + " = " + result);
return result;
}
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
BindContext context, Exception error) {
System.err.println("Failed to bind: " + name);
return null;
}
};
return binder.bindOrCreate(prefix, Bindable.of(configClass), handler);
}
public void listAllConfigurationProperties() {
ApplicationContext context = (ApplicationContext) environment;
Map<String, ConfigurationPropertiesBean> beans =
ConfigurationPropertiesBean.getAll(context);
beans.forEach((name, bean) -> {
System.out.println("Configuration Bean: " + name);
System.out.println(" Prefix: " + bean.getAnnotation().prefix());
System.out.println(" Type: " + bean.getInstance().getClass().getSimpleName());
});
}
}Application Properties File:
# Database configuration
app.database.url=jdbc:postgresql://localhost:5432/myapp
app.database.username=dbuser
app.database.password=secret
app.database.max-connections=20
app.database.pool.min-idle=5
app.database.pool.max-idle=15
app.database.pool.max-wait=10s
# Service configuration
app.service.name=my-service
app.service.timeout=60
app.service.enabled=true
app.service.endpoints[0]=http://api1.example.com
app.service.endpoints[1]=http://api2.example.com
# Cache configuration
app.cache.type=redis
app.cache.ttl=2h
app.cache.config.host=localhost
app.cache.config.port=6379
# Legacy configuration
app.legacy.old-endpoint=http://old.api.com
app.legacy.new-endpoint=https://new.api.com
# Additional properties
app.name=My Application
app.servers[0]=server1.example.com
app.servers[1]=server2.example.com
app.limits.requests=1000
app.limits.connections=100Use Java records with constructor binding for type-safe, immutable configuration.
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.constraints.*;
import java.time.Duration;
import java.util.List;
@ConfigurationProperties(prefix = "app.security")
@Validated
public record SecurityProperties(
@NotBlank(message = "JWT secret is required")
@Size(min = 32, message = "JWT secret must be at least 32 characters")
String jwtSecret,
@NotNull
@Min(value = 60, message = "Token validity must be at least 60 seconds")
Duration tokenValidity,
@NotEmpty(message = "At least one allowed origin is required")
List<@Pattern(regexp = "https?://.*", message = "Invalid URL format") String> allowedOrigins,
@Min(0) @Max(10)
int maxLoginAttempts,
@NestedConfigurationProperty
RateLimitConfig rateLimit
) {
// Nested configuration
public record RateLimitConfig(
@Min(1) int requestsPerMinute,
@NotNull Duration lockoutDuration
) {}
// Computed properties
public long getTokenValidityMillis() {
return tokenValidity.toMillis();
}
// Validation logic
public boolean isOriginAllowed(String origin) {
return allowedOrigins.stream()
.anyMatch(allowed -> origin.startsWith(allowed));
}
}
// Enable in configuration
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityConfiguration {
@Bean
public JwtTokenProvider jwtTokenProvider(SecurityProperties properties) {
return new JwtTokenProvider(
properties.jwtSecret(),
properties.tokenValidity()
);
}
}application.yml:
app:
security:
jwt-secret: ${JWT_SECRET:your-secret-key-must-be-very-long-and-secure}
token-validity: 24h
allowed-origins:
- https://app.example.com
- https://admin.example.com
max-login-attempts: 5
rate-limit:
requests-per-minute: 100
lockout-duration: 15mUse Cases:
Implement configuration hot-reload using Spring Cloud Config or custom refresh logic.
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicReference;
@ConfigurationProperties(prefix = "app.feature-flags")
@RefreshScope
public class FeatureFlagProperties {
private boolean newCheckoutEnabled;
private boolean experimentalSearchEnabled;
private int searchResultLimit = 50;
private Map<String, Boolean> regionalFeatures = new HashMap<>();
// Getters and setters
public boolean isNewCheckoutEnabled() { return newCheckoutEnabled; }
public void setNewCheckoutEnabled(boolean enabled) { this.newCheckoutEnabled = enabled; }
public boolean isExperimentalSearchEnabled() { return experimentalSearchEnabled; }
public void setExperimentalSearchEnabled(boolean enabled) {
this.experimentalSearchEnabled = enabled;
}
public int getSearchResultLimit() { return searchResultLimit; }
public void setSearchResultLimit(int limit) { this.searchResultLimit = limit; }
public Map<String, Boolean> getRegionalFeatures() { return regionalFeatures; }
public void setRegionalFeatures(Map<String, Boolean> features) {
this.regionalFeatures = features;
}
}
@Component
public class FeatureFlagService {
private final AtomicReference<FeatureFlagProperties> properties;
public FeatureFlagService(FeatureFlagProperties properties) {
this.properties = new AtomicReference<>(properties);
}
public boolean isFeatureEnabled(String featureName) {
FeatureFlagProperties current = properties.get();
return switch (featureName) {
case "new-checkout" -> current.isNewCheckoutEnabled();
case "experimental-search" -> current.isExperimentalSearchEnabled();
default -> current.getRegionalFeatures()
.getOrDefault(featureName, false);
};
}
public int getSearchLimit() {
return properties.get().getSearchResultLimit();
}
// Called by RefreshScope when configuration changes
@EventListener(RefreshScopeRefreshedEvent.class)
public void onRefresh(RefreshScopeRefreshedEvent event) {
log.info("Feature flags refreshed");
// Update atomic reference if needed
}
}
// Configuration refresh endpoint
@RestController
@RequestMapping("/admin/config")
public class ConfigRefreshController {
private final RefreshEndpoint refreshEndpoint;
public ConfigRefreshController(RefreshEndpoint refreshEndpoint) {
this.refreshEndpoint = refreshEndpoint;
}
@PostMapping("/refresh")
public Collection<String> refresh() {
return refreshEndpoint.refresh();
}
}application.yml:
app:
feature-flags:
new-checkout-enabled: false
experimental-search-enabled: true
search-result-limit: 50
regional-features:
europe-premium: true
asia-beta: false
management:
endpoints:
web:
exposure:
include: refreshUse Cases:
Manage environment-specific configurations using Spring profiles.
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
// Base properties applicable to all environments
@ConfigurationProperties(prefix = "app")
public class ApplicationProperties {
private String name;
private String version;
private DatabaseConfig database;
private CacheConfig cache;
public static class DatabaseConfig {
private String url;
private String username;
private String password;
private PoolConfig pool;
public static class PoolConfig {
private int minSize = 5;
private int maxSize = 20;
private Duration maxWait = Duration.ofSeconds(30);
// Getters and setters
}
// Getters and setters
}
public static class CacheConfig {
private String type;
private Duration ttl;
private Map<String, String> config = new HashMap<>();
// Getters and setters
}
// Getters and setters
}
// Development-specific overrides
@Configuration
@Profile("development")
@EnableConfigurationProperties(DevelopmentProperties.class)
public class DevelopmentConfiguration {
@Bean
public ApplicationProperties developmentProperties(DevelopmentProperties devProps) {
// Merge with base properties or use dev-specific settings
return devProps.toApplicationProperties();
}
}
@ConfigurationProperties(prefix = "app.dev")
class DevelopmentProperties {
private boolean debugMode = true;
private boolean hotReload = true;
private String mockDataSource;
public ApplicationProperties toApplicationProperties() {
ApplicationProperties props = new ApplicationProperties();
// Configure with dev-specific settings
return props;
}
// Getters and setters
}
// Production-specific configuration
@Configuration
@Profile("production")
@EnableConfigurationProperties(ProductionProperties.class)
public class ProductionConfiguration {
@Bean
public ApplicationProperties productionProperties(ProductionProperties prodProps) {
return prodProps.toApplicationProperties();
}
}
@ConfigurationProperties(prefix = "app.prod")
class ProductionProperties {
private SecurityConfig security;
private MonitoringConfig monitoring;
public static class SecurityConfig {
private boolean sslEnabled = true;
private String certificatePath;
private List<String> allowedOrigins;
// Getters and setters
}
public static class MonitoringConfig {
private boolean metricsEnabled = true;
private boolean tracingEnabled = true;
private String exporterEndpoint;
// Getters and setters
}
public ApplicationProperties toApplicationProperties() {
// Configure with production-specific settings
return new ApplicationProperties();
}
// Getters and setters
}application-development.yml:
app:
name: My App (Dev)
database:
url: jdbc:h2:mem:devdb
username: sa
password: ""
pool:
min-size: 1
max-size: 5
cache:
type: simple
dev:
debug-mode: true
hot-reload: true
mock-data-source: classpath:mock-data.jsonapplication-production.yml:
app:
name: My App
database:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
pool:
min-size: 10
max-size: 50
max-wait: 60s
cache:
type: redis
ttl: 1h
config:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
prod:
security:
ssl-enabled: true
certificate-path: /etc/ssl/certs/app.crt
allowed-origins:
- https://app.example.com
monitoring:
metrics-enabled: true
tracing-enabled: true
exporter-endpoint: http://prometheus:9090Use Cases:
Clean configuration of third-party libraries using PropertyMapper.
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
@ConfigurationProperties(prefix = "app.datasource")
public class DataSourceProperties {
private String url;
private String username;
private String password;
private String driverClassName;
private PoolProperties pool = new PoolProperties();
public static class PoolProperties {
private Integer minimumIdle;
private Integer maximumPoolSize;
private Long connectionTimeout;
private Long idleTimeout;
private Long maxLifetime;
private String connectionTestQuery;
private Boolean autoCommit;
private String poolName;
// Getters and setters
}
// Getters and setters
}
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceConfiguration {
@Bean
public DataSource dataSource(DataSourceProperties properties) {
HikariConfig config = new HikariConfig();
// Use PropertyMapper for clean, null-safe mapping
PropertyMapper map = PropertyMapper.get();
// Basic properties with null-safe mapping
map.from(properties::getUrl).whenNonNull().to(config::setJdbcUrl);
map.from(properties::getUsername).whenNonNull().to(config::setUsername);
map.from(properties::getPassword).whenNonNull().to(config::setPassword);
map.from(properties::getDriverClassName).whenNonNull().to(config::setDriverClassName);
// Pool properties with type conversion
DataSourceProperties.PoolProperties pool = properties.getPool();
map.from(pool::getMinimumIdle).to(config::setMinimumIdle);
map.from(pool::getMaximumPoolSize).to(config::setMaximumPoolSize);
map.from(pool::getConnectionTimeout).to(config::setConnectionTimeout);
map.from(pool::getIdleTimeout).to(config::setIdleTimeout);
map.from(pool::getMaxLifetime).to(config::setMaxLifetime);
map.from(pool::getConnectionTestQuery).to(config::setConnectionTestQuery);
map.from(pool::getAutoCommit).to(config::setAutoCommit);
map.from(pool::getPoolName).to(config::setPoolName);
// Conditional mapping
map.from(pool::getPoolName)
.when(name -> name != null && !name.isBlank())
.to(config::setPoolName);
return new HikariDataSource(config);
}
}
// Alternative: Using PropertyMapper for custom transformations
@Configuration
public class RedisConfiguration {
@Bean
public RedisClient redisClient(RedisProperties properties) {
RedisClientConfig config = new RedisClientConfig();
PropertyMapper map = PropertyMapper.get();
// With transformation
map.from(properties::getHost).to(config::setHost);
map.from(properties::getPort).to(config::setPort);
// Conditional with transformation
map.from(properties::getPassword)
.whenHasText()
.as(password -> encryptPassword(password))
.to(config::setPassword);
// With default value
map.from(properties::getDatabase)
.whenNonNull()
.to(config::setDatabase);
return new RedisClient(config);
}
private String encryptPassword(String password) {
// Encryption logic
return password;
}
}application.yml:
app:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: dbuser
password: ${DB_PASSWORD}
driver-class-name: org.postgresql.Driver
pool:
minimum-idle: 5
maximum-pool-size: 20
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
auto-commit: true
pool-name: MyAppPoolUse Cases:
Handle property migrations gracefully with deprecation warnings.
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
@ConfigurationProperties(prefix = "app.api")
public class ApiProperties {
private String endpoint;
private Duration timeout = Duration.ofSeconds(30);
private RetryConfig retry = new RetryConfig();
// Deprecated property with migration path
private String oldEndpoint;
public String getEndpoint() {
// Prefer new property, fallback to old
return endpoint != null ? endpoint : oldEndpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
@Deprecated
@DeprecatedConfigurationProperty(
replacement = "app.api.endpoint",
reason = "Renamed for consistency with other endpoint properties"
)
public String getOldEndpoint() {
return oldEndpoint;
}
public void setOldEndpoint(String oldEndpoint) {
this.oldEndpoint = oldEndpoint;
}
public Duration getTimeout() { return timeout; }
public void setTimeout(Duration timeout) { this.timeout = timeout; }
public RetryConfig getRetry() { return retry; }
public void setRetry(RetryConfig retry) { this.retry = retry; }
public static class RetryConfig {
private int maxAttempts = 3;
private Duration backoff = Duration.ofMillis(100);
// Getters and setters
}
}
// Environment post-processor for automatic migration
public class PropertyMigrationPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
Map<String, Object> migrations = new HashMap<>();
// Check for old properties and migrate
String oldEndpoint = environment.getProperty("app.api.old-endpoint");
if (oldEndpoint != null &&
environment.getProperty("app.api.endpoint") == null) {
migrations.put("app.api.endpoint", oldEndpoint);
log.warn("Property 'app.api.old-endpoint' is deprecated. " +
"Please use 'app.api.endpoint' instead.");
}
// Check for renamed properties
String legacyTimeout = environment.getProperty("app.api.connection-timeout");
if (legacyTimeout != null &&
environment.getProperty("app.api.timeout") == null) {
migrations.put("app.api.timeout", legacyTimeout);
log.warn("Property 'app.api.connection-timeout' is deprecated. " +
"Please use 'app.api.timeout' instead.");
}
if (!migrations.isEmpty()) {
MapPropertySource migrationSource =
new MapPropertySource("configurationPropertiesMigration", migrations);
environment.getPropertySources().addFirst(migrationSource);
}
}
}
// Validation that ensures migration
@Component
public class ConfigurationMigrationValidator implements ApplicationRunner {
private final Environment environment;
public ConfigurationMigrationValidator(Environment environment) {
this.environment = environment;
}
@Override
public void run(ApplicationArguments args) {
List<String> deprecatedProperties = Arrays.asList(
"app.api.old-endpoint",
"app.api.connection-timeout"
);
List<String> foundDeprecated = new ArrayList<>();
for (String property : deprecatedProperties) {
if (environment.containsProperty(property)) {
foundDeprecated.add(property);
}
}
if (!foundDeprecated.isEmpty()) {
log.warn("\n" +
"=================================================================\n" +
"DEPRECATION WARNING: The following properties are deprecated:\n" +
String.join("\n", foundDeprecated) + "\n" +
"Please update your configuration. Support will be removed in v2.0\n" +
"=================================================================");
}
}
}application.yml (old format):
app:
api:
old-endpoint: https://api.example.com # Deprecated
connection-timeout: 60s # Deprecatedapplication.yml (new format):
app:
api:
endpoint: https://api.example.com
timeout: 60s
retry:
max-attempts: 3
backoff: 100msUse Cases:
@ConfigurationProperties over @Value for complex configuration@Validated with JSR-303 annotations (@NotNull, @Min, @Max, etc.)@DeprecatedConfigurationProperty when deprecating propertiesPropertyMapper for clean, declarative mapping@NestedConfigurationPropertyapp.*, company.*)@ConfigurationProperties on demand, not eagerly loaded@SpringBootTest(properties = {
"app.security.jwt-secret=test-secret-key-for-testing-only",
"app.security.token-validity=1h",
"app.security.allowed-origins=http://localhost:3000"
})
class SecurityPropertiesTest {
@Autowired
private SecurityProperties properties;
@Test
void shouldBindProperties() {
assertThat(properties.jwtSecret()).isEqualTo("test-secret-key-for-testing-only");
assertThat(properties.tokenValidity()).isEqualTo(Duration.ofHours(1));
assertThat(properties.allowedOrigins()).contains("http://localhost:3000");
}
@Test
void shouldValidateInvalidProperties() {
// Test validation logic
}
}spring-configuration-metadata.json for IDE support