Why SpringBoot Replaced spring.factories with a Faster, GraalVM‑Friendly Imports Mechanism
SpringBoot 3.0 drops the long‑standing spring.factories file in favor of per‑extension imports files, eliminating full‑classpath scans, improving startup speed, aligning with Java modules, and enabling seamless GraalVM native‑image support, while providing a clear migration path for existing projects.
Background: the role of spring.factories
In SpringBoot 2.x, spring.factories was a Java‑SPI‑style configuration file placed under META-INF/. It listed key‑value pairs that told SpringBoot which implementation classes to load for a given extension point, enabling the framework’s “convention over configuration” approach.
How spring.factories worked
At application start‑up, SpringFactoriesLoader scanned every JAR on the classpath, read all META-INF/spring.factories files, parsed the entries, and loaded the corresponding classes. This mechanism supported automatic configuration, ApplicationListener registration, and third‑party component integration.
Why spring.factories became a liability
Slow startup : The loader scans every JAR regardless of whether the entries are needed, causing noticeable CPU and I/O overhead in large microservice projects.
Incompatible with Java 9+ modules : The runtime‑only class‑path scanning conflicts with JPMS’s explicit dependency declarations, making module boundaries unclear.
Eager loading then filtering : All classes are loaded before @Conditional checks can discard unsuitable ones, wasting resources.
Scattered configuration : Each dependency may contain its own spring.factories, turning configuration into a set of hidden “paper notes” that are hard to locate and prone to conflicts.
GraalVM native‑image incompatibility : The runtime scanning prevents static analysis, requires extensive reflection configuration, and mismatches resource‑access patterns, blocking native‑image builds.
New solution: imports files
SpringBoot 3.0 introduces a set of .imports files under META-INF/spring/. Each extension point gets its own file (e.g., AutoConfiguration.imports, ApplicationContextInitializer.imports, FailureAnalyzer.imports) containing one fully‑qualified class name per line.
Four main advantages
Faster startup : Only the files relevant to the required extension points are read, eliminating the full‑classpath scan.
Module‑system friendly : The META-INF/spring directory can be opened via JPMS opens declarations, satisfying explicit module dependencies.
Simpler configuration : The key‑value syntax disappears; developers write a plain list of class names, reducing formatting errors.
Native‑image ready : The static file structure allows GraalVM’s build‑time analysis to determine all required classes without extra reflection metadata.
Side‑by‑side comparison
Old spring.factories entry (auto‑configuration):
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.FooAutoConfiguration,\
com.example.BarAutoConfigurationNew AutoConfiguration.imports file:
com.example.FooAutoConfiguration
com.example.BarAutoConfigurationMigration guide for auto‑configuration
Leave existing annotations ( @Configuration, @ConditionalOn…) unchanged.
Create the directory resources/META-INF/spring/ and add a file named
org.springframework.boot.autoconfigure.AutoConfiguration.imports.
List each auto‑configuration class on its own line, e.g., com.example.FooAutoConfiguration.
Other extension points
For ApplicationListener, FailureAnalyzer, etc., you can either create the corresponding .imports file (recommended) or register the bean manually with @Bean in a configuration class.
Example using an imports file
com.example.MyApplicationListenerExample using @Bean
@Configuration
public class ListenerConfig {
@Bean
public MyApplicationListener myApplicationListener() {
return new MyApplicationListener();
}
}Custom loader migration
Old loader using SpringFactoriesLoader:
public class MyExtensionLoader {
public List<MyExtension> loadExtensions() {
// Load implementations from spring.factories
return SpringFactoriesLoader.loadFactories(MyExtension.class, null);
}
}New loader reading a custom .imports file:
public class MyExtensionLoader {
public List<MyExtension> loadExtensions() {
List<String> classNames = loadFromImportsFile("META-INF/spring/MyExtension.imports");
return classNames.stream()
.map(className -> ClassUtils.forName(className, null).newInstance())
.collect(Collectors.toList());
}
}SpringFactoriesLoader API changes
The original loadFactories method is now deprecated. A new method loadFactoryNames returns only class names, leaving instantiation to the caller for greater flexibility.
Compatibility notes
For backward compatibility, SpringBoot 3.0 still parses spring.factories for core extension points like EnableAutoConfiguration. However, new projects should adopt the imports files, and future releases will drop the compatibility layer.
Conclusion
The retirement of spring.factories is not a judgment of its past usefulness but a response to evolving requirements: performance, JPMS compatibility, and GraalVM native‑image support. The imports mechanism delivers a static, categorized, and efficient configuration model that aligns SpringBoot with the cloud‑native era.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
