or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

admin-jmx.mdansi-support.mdaot-native-image.mdapplication-info.mdavailability.mdbootstrap.mdbootstrapping.mdbuilder.mdcloud-platform.mdconfiguration-annotations.mdconfiguration-data.mdconfiguration-properties.mdconversion.mddiagnostics.mdenvironment-property-sources.mdindex.mdjson-support.mdlifecycle-events.mdlogging.mdorigin-tracking.mdresource-loading.mdretry-support.mdssl-tls.mdstartup-metrics.mdsupport.mdsystem-utilities.mdtask-execution.mdthreading.mdutilities.mdvalidation.mdweb-support.md
tile.json

configuration-annotations.mddocs/

Configuration Annotations Support

Configuration management and testing support for programmatically working with @Configuration classes and importing configuration candidates from the classpath.

Package Information

Package: org.springframework.boot.context.annotation

Core Imports

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;

Overview

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.

Capabilities

Configurations 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);

User Configuration Classes

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_PRECEDENCE

Import Candidates Discovery

ImportCandidates 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.MessagingConfig

Usage 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.imports
  • com.example.EnableCustomAutoConfiguration.imports

Determinable Imports Interface

DeterminableImports 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:

  1. Better Test Performance: Spring's test framework can cache ApplicationContexts more effectively
  2. Predictable Behavior: Imports are determined consistently from the same source
  3. Cache Key Generation: Enables proper cache key computation for contexts

When to Implement:

Implement DeterminableImports when your ImportSelector or ImportBeanDefinitionRegistrar:

  • Always produces the same imports for the same annotation metadata
  • Doesn't depend on external state or runtime conditions
  • Can determine imports without requiring Aware callbacks

Component Scan Type Exclusion

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:

  1. 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.

  2. Early Initialization: TypeExcludeFilters are initialized very early in the application lifecycle. They should generally not have dependencies on other beans.

  3. Primary Use Case: Primarily used internally to support spring-boot-test, but available for custom component scanning exclusions.

  4. Thread Safety: Implementation should be thread-safe and immutable where possible.

When to Use:

  • Excluding test utilities from production component scanning
  • Filtering out experimental or internal packages
  • Conditional component exclusion based on metadata
  • Test-specific scanning configurations

Common Use Cases

Testing with Programmatic Configuration

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");
        });
    }
}

Loading Custom Auto-Configurations

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.SecurityAutoConfiguration

Dynamic Configuration Selection

import 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();
    }
}

Best Practices

  1. Use for Testing: These classes are primarily designed for programmatic configuration in tests
  2. Respect Ordering: UserConfigurations have LOWEST_PRECEDENCE by default
  3. Implement DeterminableImports: When your ImportSelector behavior is predictable
  4. File Format: Use proper .imports file format with comments and blank line support
  5. ClassLoader Handling: Pass null to use the default classloader in most cases

Relation to Other Components

  • Spring Test Framework: Used by ApplicationContextRunner for test context configuration
  • Auto-Configuration: ImportCandidates is the mechanism Spring Boot uses internally for auto-configuration discovery
  • Context Caching: DeterminableImports enables better ApplicationContext cache key generation in tests

Thread Safety

  • ImportCandidates instances are immutable and thread-safe
  • Configurations instances are immutable and thread-safe
  • load() method can be called concurrently from multiple threads

Version Information

  • Configurations: @since 2.0.0
  • UserConfigurations: @since 2.0.0
  • ImportCandidates: @since 2.7.0
  • DeterminableImports: @since 1.5.0