Spring Boot SPI and AutoConfiguration.imports Technical Guide
Overview
Service Provider Interface (SPI) is a mechanism in Spring Boot that allows runtime discovery and loading of service implementations. This guide covers both the traditional SPI mechanism and the modern AutoConfiguration.imports approach introduced in Spring Boot 2.7.
Table of Contents
Spring Boot SPI Fundamentals
Core Components
Spring Boot SPI consists of four main components:
- Service Interface - Defines the contract (interface/abstract class)
- Service Provider - Concrete implementation of the interface
- META-INF Configuration - Files listing implementations
- ServiceLoader/ImportSelector - Runtime discovery mechanism
Traditional Process Flow
1. Create service interface: com.example.MyService
2. Implement the service: com.example.MyServiceImpl
3. Create file: META-INF/services/com.example.MyService
4. File content: com.example.MyServiceImpl
5. Load at runtime: ServiceLoader.load(MyService.class)
Spring Boot Auto-Configuration Process
1. @SpringBootApplication → @EnableAutoConfiguration
2. AutoConfigurationImportSelector.selectImports()
3. Load configuration candidates from META-INF files
4. Apply conditional filtering
5. Register matching beans
AutoConfiguration.imports Technical Implementation
File Structure & Location
Spring Boot 2.7+ (Recommended):
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
Legacy Spring Boot 2.x:
META-INF/spring.factories
Core Processing Components
AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements
DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
// 1. Check if auto-configuration is enabled
// 2. Load candidates from AutoConfiguration.imports
// 3. Apply exclusions and filters
// 4. Return filtered configuration class names
}
}
ImportCandidates (New in 2.7)
public final class ImportCandidates implements Iterable<String> {
private static final String LOCATION =
"META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports";
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
// Loads from AutoConfiguration.imports files
}
}
Processing Pipeline
Phase 1: Discovery
// Scan classpath for AutoConfiguration.imports files
// Read all FQCN entries and deduplicate across JARs
ImportCandidates candidates = ImportCandidates.load(EnableAutoConfiguration.class, classLoader);
Phase 2: Metadata Loading - Deep Dive
The AutoConfigurationMetadataLoader
loads pre-compiled metadata from META-INF/spring-autoconfigure-metadata.properties
to optimize startup performance.
How Metadata is Generated:
// During compilation, Spring Boot's annotation processor scans:
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(EntityManagerFactory.class)
@ConditionalOnProperty(name = "spring.jpa.enabled", matchIfMissing = true)
public class JpaAutoConfiguration {
// Configuration
}
Generated Metadata File (spring-autoconfigure-metadata.properties
):
# Auto-generated by Spring Boot annotation processor
org.springframework.boot.autoconfigure.orm.jpa.JpaAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,javax.persistence.EntityManager
[... omitted 155 of 411 lines ...]
ClassLoader getClassLoader();
}
- ConditionOutcome - Represents the result of condition evaluation:
public class ConditionOutcome {
private final boolean match;
private final ConditionMessage message;
public static ConditionOutcome match(String message) {
return new ConditionOutcome(true, message);
}
public static ConditionOutcome noMatch(String message) {
return new ConditionOutcome(false, message);
}
}
Configuration Phases:
Spring Boot evaluates conditions in two phases:
- PARSE_CONFIGURATION Phase: Evaluates during configuration class parsing (used for
@ConditionalOnClass
,@ConditionalOnProperty
) - REGISTER_BEAN Phase: Evaluates during bean registration (used for
@ConditionalOnBean
,@ConditionalOnMissingBean
)
public enum ConfigurationPhase {
PARSE_CONFIGURATION,
REGISTER_BEAN
}
@Component
public class OnBeanCondition implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN; // Wait for other beans
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Check if required beans exist
}
}
Real Example: DataSource Auto-Configuration:
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "javax.sql.DataSource")
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "spring.datasource.type")
public DataSource dataSource(DataSourceProperties properties) {
return DataSourceBuilder.create()
.driverClassName(properties.getDriverClassName())
.url(properties.getUrl())
.username(properties.getUsername())
.password(properties.getPassword())
.build();
}
}
Evaluation Process:
- Phase 1 (PARSE_CONFIGURATION): Check if
DataSource.class
exists on classpath - Phase 2 (REGISTER_BEAN): Check if
DataSource
bean already exists, verify properties - Result: Create DataSource bean if conditions pass
Performance Characteristics:
- Efficient Ordering: Fast conditions first (classpath checks), slower conditions last (bean registry lookups)
- Caching Results: Spring caches condition outcomes to avoid re-evaluation
- Error Handling: Detailed condition reporting for debugging
Technical Improvements
Performance Optimization
- Lazy Loading: Classes loaded only when conditions might match
- Metadata Pre-filtering: Uses
spring-autoconfigure-metadata.properties
- Reduced String Parsing: No key=value parsing overhead
- 30-40% faster startup due to reduced parsing overhead
Memory Efficiency
// Old: Properties object with key-value pairs
Properties springFactories = loadSpringFactories();
// New: Simple List<String>
List<String> imports = loadImports();
GraalVM Native Support
- Static Analysis Friendly: Plain file format easier to analyze
- No Reflection for Discovery: Direct file reading
- Predictable Class Loading: Explicit import list
Execution Timeline
1. @SpringBootApplication → @EnableAutoConfiguration
2. AutoConfigurationImportSelector.selectImports()
3. ImportCandidates.load() → Read .imports files
4. AutoConfigurationMetadataLoader → Load metadata
5. Early filtering based on metadata
6. Class loading + condition evaluation
7. Bean registration for matching configs
Migration Guide
From spring.factories to AutoConfiguration.imports
Legacy spring.factories (Spring Boot 2.x)
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.redis.RedisAutoConfiguration,\
com.example.kafka.KafkaAutoConfiguration
Modern AutoConfiguration.imports (Spring Boot 2.7+)
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.redis.RedisAutoConfiguration
com.example.kafka.KafkaAutoConfiguration
Backwards Compatibility
Libraries targeting both Spring Boot 2.x and 3.x can list configurations in both locations:
spring.factories
(backward compatibility)AutoConfiguration.imports
(performance optimization)
Spring Boot 2.7+ automatically deduplicates entries found in both files.
Migration Steps
-
Create new imports file:
bashmkdir -p src/main/resources/META-INF/spring touch src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
-
Copy configuration class names from spring.factories to imports file (one per line)
-
Update annotations: Replace
@Configuration
with@AutoConfiguration
on top-level auto-configuration classes -
Test compatibility with both Spring Boot 2.x and 3.x if needed
-
Remove spring.factories entries when dropping Spring Boot 2.x support
Comments