docs
Configuration management and testing support for programmatically working with @Configuration classes and importing configuration candidates from the classpath.
Package: org.springframework.boot.context.annotation
import org.springframework.boot.context.annotation.Configurations;
import org.springframework.boot.context.annotation.UserConfigurations;
import org.springframework.boot.context.annotation.ImportCandidates;
import org.springframework.boot.context.annotation.DeterminableImports;This package provides utilities for managing and discovering @Configuration classes programmatically. It is primarily designed for testing scenarios where you need to specify configuration classes but can't use standard Spring test runners, though it's also useful for dynamic configuration management.
The Configurations class provides an abstraction for managing sets of @Configuration classes with proper ordering and merging support.
/**
* A set of @Configuration classes that can be registered in ApplicationContext.
* Classes are returned with proper ordering applied.
*/
public abstract class Configurations {
/**
* Merge configurations from multiple sources
*/
public static Class<?>[] getClasses(Configurations... configurations);
/**
* Get configuration classes from this instance
*/
protected Set<Class<?>> getClasses();
/**
* Merge with other configuration sets
*/
protected abstract Configurations merge(Set<Class<?>> mergedClasses);
}Usage Example:
import org.springframework.boot.context.annotation.Configurations;
import org.springframework.boot.context.annotation.UserConfigurations;
// Get ordered configuration classes from multiple sources
Configurations userConfigs = UserConfigurations.of(
MyAppConfig.class,
DatabaseConfig.class,
SecurityConfig.class
);
// Merge and get ordered classes
Class<?>[] orderedClasses = Configurations.getClasses(userConfigs);UserConfigurations represents user-defined @Configuration classes with lowest priority ordering.
/**
* Configurations representing user-defined @Configuration classes
* @since 2.0.0
*/
public class UserConfigurations extends Configurations implements PriorityOrdered {
/**
* Create UserConfigurations from configuration classes
*/
public static UserConfigurations of(Class<?>... classes);
/**
* Returns LOWEST_PRECEDENCE order
*/
@Override
public int getOrder();
}Usage Example:
import org.springframework.boot.context.annotation.UserConfigurations;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
@Configuration
class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
@Configuration
class DatabaseConfig {
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
}
// Create user configurations programmatically
UserConfigurations configs = UserConfigurations.of(
AppConfig.class,
DatabaseConfig.class
);
// Use in tests or programmatic context creation
// These configurations will be ordered with LOWEST_PRECEDENCEImportCandidates provides a mechanism to discover configuration import candidates from META-INF/spring/*.imports files on the classpath.
/**
* Contains @Configuration import candidates loaded from classpath
* @since 2.7.0
*/
public final class ImportCandidates implements Iterable<String> {
/**
* Loads import candidates from classpath for the given annotation.
* Reads from META-INF/spring/fully-qualified-annotation-name.imports
*
* @param annotation annotation class to load candidates for
* @param classLoader class loader to use (null uses default)
* @return ImportCandidates containing discovered class names
*/
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader);
/**
* Returns the list of loaded import candidates
*/
public List<String> getCandidates();
/**
* Iterate over candidate class names
*/
@Override
public Iterator<String> iterator();
}File Format:
The .imports files follow this format:
# Comments start with #
com.example.config.WebConfig
com.example.config.DatabaseConfig
com.example.config.SecurityConfig
# Blank lines are ignored
com.example.config.MessagingConfigUsage Example:
import org.springframework.boot.context.annotation.ImportCandidates;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
// Load auto-configuration candidates
ImportCandidates candidates = ImportCandidates.load(
EnableAutoConfiguration.class,
getClass().getClassLoader()
);
// Process discovered candidates
for (String candidate : candidates) {
System.out.println("Found configuration: " + candidate);
}
// Or get as list
List<String> candidateList = candidates.getCandidates();
// Example use case: Load custom auto-configurations
// File: META-INF/spring/com.example.MyAutoConfiguration.imports
// Contents:
// com.example.config.Config1
// com.example.config.Config2
ImportCandidates myConfigs = ImportCandidates.load(
MyAutoConfiguration.class,
null // uses default classloader
);File Location Convention:
Files are located at: META-INF/spring/{fully-qualified-annotation-name}.imports
For example:
org.springframework.boot.autoconfigure.EnableAutoConfiguration.importscom.example.EnableCustomAutoConfiguration.importsDeterminableImports allows ImportSelector and ImportBeanDefinitionRegistrar implementations to declare their imports upfront for better caching and testing support.
/**
* Interface for ImportSelector/ImportBeanDefinitionRegistrar implementations
* that can determine imports early for better testing support and context caching
* @since 1.5.0
*/
@FunctionalInterface
public interface DeterminableImports {
/**
* Return a set of objects representing the imports.
* Objects must implement valid hashCode() and equals().
* Aware callbacks are NOT invoked before this method.
*
* @param metadata the source annotation metadata
* @return set of objects representing imports for cache key generation
*/
Set<Object> determineImports(AnnotationMetadata metadata);
}Usage Example:
import org.springframework.boot.context.annotation.DeterminableImports;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import java.util.Set;
/**
* ImportSelector that can determine its imports early for better caching
*/
public class MyImportSelector implements ImportSelector, DeterminableImports {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// Actual import logic
return new String[] {
"com.example.config.Config1",
"com.example.config.Config2"
};
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
// Return consistent cache key for these imports
// This is called WITHOUT Aware callbacks, so must be purely metadata-based
return Set.of(
"com.example.config.Config1",
"com.example.config.Config2"
);
}
}Benefits:
When to Implement:
Implement DeterminableImports when your ImportSelector or ImportBeanDefinitionRegistrar:
The TypeExcludeFilter provides an extensible mechanism for excluding types from component scanning. It loads exclusion filters from the BeanFactory and automatically applies them to @SpringBootApplication scanning.
package org.springframework.boot.context;
import java.io.IOException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
/**
* Provides exclusion TypeFilters that are loaded from the BeanFactory
* and automatically applied to SpringBootApplication scanning.
*
* @since 1.4.0
*/
public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware {
/**
* Set the BeanFactory used to load delegate filters.
*
* @param beanFactory the bean factory
*/
@Override
public void setBeanFactory(BeanFactory beanFactory);
/**
* Determine if this filter matches the given type.
* Delegates to all TypeExcludeFilter beans in the factory.
*
* @param metadataReader the metadata reader for the target class
* @param metadataReaderFactory a factory for obtaining metadata readers
* @return true if the type should be excluded
* @throws IOException on IO errors
*/
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory)
throws IOException;
/**
* Subclasses MUST override this method to provide proper equality semantics.
* Required for Spring test application context caching.
*
* @param obj the object to compare
* @return true if equal
* @throws IllegalStateException if not overridden by subclass
*/
@Override
public boolean equals(Object obj);
/**
* Subclasses MUST override this method to provide proper hash code.
* Required for Spring test application context caching.
*
* @return the hash code
* @throws IllegalStateException if not overridden by subclass
*/
@Override
public int hashCode();
}Usage with @ComponentScan:
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@ComponentScan(excludeFilters = @ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = TypeExcludeFilter.class))
public class MyConfiguration {
}Creating Custom Exclude Filters:
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Objects;
@Component
public class TestComponentExcludeFilter extends TypeExcludeFilter {
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory)
throws IOException {
// Exclude test components from production builds
String className = metadataReader.getClassMetadata().getClassName();
return className.contains("Test") || className.contains("Mock");
}
@Override
public boolean equals(Object obj) {
// REQUIRED: Must implement for test context caching
return (obj != null && getClass() == obj.getClass());
}
@Override
public int hashCode() {
// REQUIRED: Must implement for test context caching
return Objects.hash(getClass());
}
}Advanced Example: Package-based Exclusion:
@Component
public class PackageExcludeFilter extends TypeExcludeFilter {
private final Set<String> excludedPackages;
public PackageExcludeFilter() {
this.excludedPackages = Set.of(
"com.example.internal",
"com.example.experimental"
);
}
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory)
throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return excludedPackages.stream()
.anyMatch(className::startsWith);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof PackageExcludeFilter other)) return false;
return Objects.equals(excludedPackages, other.excludedPackages);
}
@Override
public int hashCode() {
return Objects.hash(getClass(), excludedPackages);
}
}Important Implementation Requirements:
equals() and hashCode() are REQUIRED: Subclasses must override both methods. The base class throws IllegalStateException if called. This is essential for Spring test's application context caching mechanism.
Early Initialization: TypeExcludeFilters are initialized very early in the application lifecycle. They should generally not have dependencies on other beans.
Primary Use Case: Primarily used internally to support spring-boot-test, but available for custom component scanning exclusions.
Thread Safety: Implementation should be thread-safe and immutable where possible.
When to Use:
import org.springframework.boot.context.annotation.UserConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class MyServiceTest {
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withConfiguration(UserConfigurations.of(TestConfig.class));
@Configuration
static class TestConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
@Test
void testMyService() {
contextRunner.run(context -> {
assertThat(context).hasSingleBean(MyService.class);
MyService service = context.getBean(MyService.class);
assertThat(service.doSomething()).isEqualTo("expected");
});
}
}import org.springframework.boot.context.annotation.ImportCandidates;
/**
* Load custom auto-configurations from META-INF/spring/ files
*/
public class CustomAutoConfigurationLoader {
public List<String> loadAutoConfigurations() {
ImportCandidates candidates = ImportCandidates.load(
EnableCustomAutoConfiguration.class,
this.getClass().getClassLoader()
);
return candidates.getCandidates();
}
}
// File: META-INF/spring/com.example.EnableCustomAutoConfiguration.imports
// com.example.autoconfigure.DataSourceAutoConfiguration
// com.example.autoconfigure.WebMvcAutoConfiguration
// com.example.autoconfigure.SecurityAutoConfigurationimport org.springframework.boot.context.annotation.UserConfigurations;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DynamicConfigurationExample {
public void loadContext(boolean includeDatabase, boolean includeSecurity) {
List<Class<?>> configs = new ArrayList<>();
configs.add(CoreConfig.class);
if (includeDatabase) {
configs.add(DatabaseConfig.class);
}
if (includeSecurity) {
configs.add(SecurityConfig.class);
}
UserConfigurations userConfigs = UserConfigurations.of(
configs.toArray(new Class<?>[0])
);
Class<?>[] orderedConfigs = Configurations.getClasses(userConfigs);
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(orderedConfigs);
// Use context...
context.close();
}
}.imports file format with comments and blank line supportApplicationContextRunner for test context configurationImportCandidates instances are immutable and thread-safeConfigurations instances are immutable and thread-safeload() method can be called concurrently from multiple threadsConfigurations: @since 2.0.0UserConfigurations: @since 2.0.0ImportCandidates: @since 2.7.0DeterminableImports: @since 1.5.0