Why Spring Boot 3 Removed spring.factories & How to Migrate to imports
This article explains the removal of the spring.factories file in Spring Boot 3, the performance and modularity reasons behind it, introduces the new imports‑based mechanism, provides step‑by‑step migration guidance, and shows how the change improves GraalVM native image support.
1. Introduction
Spring Boot 3.0 introduces a major change by removing the
spring.factoriesfile, which was the core of auto‑configuration and extension registration in earlier versions. Understanding the new mechanism and migration strategy is essential for developers upgrading their projects.
This article explores the reasons for the change, its impact, and the replacement approach.
2. What is spring.factories?
The
spring.factoriesfile resides under
META-INF/and uses a variant of Java’s SPI mechanism to declare implementations of interfaces, enabling automatic configuration and extension point registration in Spring Boot.
2.1 Basic concept
spring.factoriesis a configuration file located in
META-INF/. It lists key‑value pairs where the key is an extension point interface and the value is a comma‑separated list of implementation class names.
2.2 Main uses (pre‑3.0)
Auto‑configuration registration
ApplicationListener registration
Other extension point declarations
2.3 How it works
During startup,
SpringFactoriesLoaderscans all JARs for
META-INF/spring.factories, reads the entries and loads the corresponding classes, following a “convention over configuration” approach.
<code>public final class SpringFactoriesLoader {
@Deprecated
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
// ...
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
// Load corresponding imports file
// ...
}
}
</code>3. Why spring.factories was removed
3.1 Performance issues
Scanning every JAR for
spring.factoriesbecomes costly in projects with many dependencies, slowing application startup.
3.2 Lack of modularity support
Java 9’s module system (JPMS) conflicts with class‑path scanning, making
spring.factoriesunsuitable for modular applications.
3.3 No conditional loading
Entries are static; they cannot be loaded conditionally at runtime without loading all classes first, which reduces efficiency.
3.4 Distributed configuration
In large projects, configurations are scattered across many JARs, making global management difficult.
3.5 GraalVM native image support
Static analysis limitation: GraalVM needs compile‑time knowledge of classes, but
spring.factoriesrelies on dynamic scanning.
Reflection usage: the mechanism depends on reflection, which requires explicit configuration for native images.
Resource access: native images handle resources differently, so the traditional scanning approach does not work.
Therefore, a static, build‑time configuration method is required.
4. Alternative: imports files
4.1 New mechanism
Since Spring Boot 3.0, an
importsfile under
META-INF/spring/replaces
spring.factories. Each extension point type has its own file, e.g.,
org.springframework.boot.autoconfigure.AutoConfiguration.imports.
4.2 Advantages
Better performance : Separate files avoid loading unnecessary configurations.
Java module compatibility : Works seamlessly with JPMS.
Simplified configuration : One fully‑qualified class name per line, no key‑value syntax.
Clearer organization : Configurations are grouped by functionality.
4.3 Example comparison
Old
spring.factoriesentry:
<code>org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.FooAutoConfiguration,\
com.example.BarAutoConfiguration
</code>New
AutoConfiguration.importsentry:
<code>com.example.FooAutoConfiguration
com.example.BarAutoConfiguration
</code>5. Migration guide
5.1 Auto‑configuration classes
Move entries from
spring.factoriesto
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
<code>// Original auto‑configuration class
@Configuration
@ConditionalOnXxx
public class MyAutoConfiguration {
// ...
}
// Add the fully‑qualified name to the imports file
// com.example.MyAutoConfiguration
</code>5.2 Other extension points
For non‑auto‑configuration extension points, keep using
spring.factoriesif necessary, but prefer the new registration style in new projects.
<code>// Register ApplicationListener (old way)
org.springframework.context.ApplicationListener=com.example.MyListener
// New way (bean registration)
@Configuration
public class MyConfiguration {
@Bean
public MyListener myListener() {
return new MyListener();
}
}
</code>5.3 Custom extension loaders
<code>public class MyExtensionLoader {
public List<MyExtension> loadExtensions() {
return SpringFactoriesLoader.loadFactories(MyExtension.class, null);
}
}
// After migration, load from imports file instead
public class MyExtensionLoader {
public List<MyExtension> loadExtensions() {
List<String> classNames = SpringFactoriesLoader.loadFactoryNames(MyExtension.class, null);
// custom loading logic
}
}
</code>6. Changes to SpringFactoriesLoader
6.1 API updates
The old
loadFactoriesmethod is deprecated; the new
loadFactoryNamesreads the imports files directly.
6.2 Compatibility
Spring Boot 3.0 still supports
spring.factoriesfor backward compatibility, but new projects should adopt the imports mechanism.
7. Practical example
7.1 Custom auto‑configuration
<code>@ConfigurationProperties(prefix = "myapp")
public class MyProperties {
private boolean enabled = true;
private String name = "default";
// getters and setters
}
@AutoConfiguration
@EnableConfigurationProperties(MyProperties.class)
@ConditionalOnProperty(prefix = "myapp", name = "enabled", havingValue = "true", matchIfMissing = true)
public class MyAutoConfiguration {
private final MyProperties properties;
public MyAutoConfiguration(MyProperties properties) { this.properties = properties; }
@Bean
@ConditionalOnMissingBean
public MyService myService() {
return new MyServiceImpl(properties.getName());
}
}
</code>7.2 Register the auto‑configuration
Create
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importscontaining:
<code>com.example.MyAutoConfiguration
</code>7.3 Project structure
<code>myproject/
├── src/main/java/com/example/
│ ├── MyProperties.java
│ ├── MyService.java
│ ├── MyServiceImpl.java
│ └── MyAutoConfiguration.java
├── src/main/resources/META-INF/spring/
│ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── pom.xml
</code>8. Performance comparison
A typical medium‑size Spring Boot application shows noticeable startup time reduction when using the new imports mechanism. (Image omitted for brevity.)
Note: Actual gains depend on project size and structure.
9. FAQ
9.1 Compatibility issues
Existing libraries that still use
spring.factoriescontinue to work because Spring Boot retains support for the old file.
9.2 Migration effort
Large projects can migrate incrementally: start with auto‑configuration classes, then move other extension points.
9.3 Custom loaders
Implement a similar imports‑based loader for custom extension points, following the new
SpringFactoriesLoader.loadFactoryNamespattern.
10. Spring Boot 3.0 and GraalVM integration
10.1 Why the change helps GraalVM
GraalVM requires static analysis; the imports files provide a deterministic list of configuration classes, eliminating dynamic class‑path scanning and reducing reflection usage.
10.2 Benefits for GraalVM
Static analyzability : All configuration classes are listed explicitly.
Clear paths : Each extension point has a dedicated file.
Less reflection : Simpler parsing reduces the need for reflective access.
Build‑time handling : Imports can be processed during AOT compilation.
10.3 AOT engine example
<code>public class SpringAotProcessor {
public void process() {
List<String> configurations = readImportsFiles();
List<String> effectiveConfigurations = evaluateConditions(configurations, buildTimeProperties);
generateProxies(effectiveConfigurations);
generateReflectionConfig(effectiveConfigurations);
generateResourcesConfig();
}
}
</code>10.4 GraalVM native image configuration
Example Maven setup for building a native image with Spring Native and the new imports mechanism.
<code><dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>${spring-native.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</code>10.5 Best practices for GraalVM
Minimize reflection; prefer constructor injection.
Avoid dynamic proxies when possible.
Initialize static data at build time.
Register all configuration classes via imports files.
Use
@NativeHintto supply GraalVM hints.
10.6 Limitations
Dynamic features (runtime bytecode generation, class loading) are restricted.
Reflection requires explicit configuration.
Native image builds are slower; plan CI/CD accordingly.
Debugging native images is more complex than JVM debugging.
Some third‑party libraries may lack GraalVM support.
By removing
spring.factoriesand adopting the imports mechanism, Spring Boot 3.0 greatly improves GraalVM native image integration, enabling developers to build high‑performance, low‑latency cloud‑native applications.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
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.