Why SpringBoot 3.0 Dropped spring.factories and How to Migrate to Imports
SpringBoot 3.0 removed the traditional spring.factories file to improve startup performance, modularity, and GraalVM native image support, and introduces a new imports‑based registration mechanism with detailed migration steps, code examples, and best‑practice guidance for developers.
Why SpringBoot 3.0 removed spring.factories
1. Introduction
During the evolution of SpringBoot, version 3.0 introduced a major change by removing the long‑standing spring.factories file, which was the core of auto‑configuration and extension mechanisms. Engineers accustomed to earlier versions need to understand the new mechanism and migration strategy.
2. What is spring.factories
Before discussing its removal, we need to understand the role of the spring.factories file.
2.1 Basic concept
spring.factoriesis a configuration file located under META-INF/. It is a variant of Java's Service Provider Interface (SPI) mechanism. Its main function is to allow developers to declare implementation classes for interfaces, enabling SpringBoot's auto‑configuration and extension‑point registration.
2.2 Main uses
Before SpringBoot 3.0, the spring.factories file had several primary uses:
2.3 How it works
When SpringBoot starts, it uses the SpringFactoriesLoader class to scan all JARs on the classpath for META-INF/spring.factories files, reads the configuration, and loads the corresponding classes. This implements the "convention over configuration" approach for auto‑configuration.
public final class SpringFactoriesLoader {
// ...
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
// Load META-INF/spring.factories configuration
Map<String, List<String>> result = loadSpringFactories(classLoader);
// ...
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// Load all META-INF/spring.factories files from the classpath
// ...
}
// ...
}3. Why spring.factories was removed
3.1 Performance issues
The mechanism scans all JARs for configuration files at startup, which can consume a lot of time in projects with many dependencies, affecting startup performance.
3.2 Lack of modular support
With Java 9's module system (JPMS), class‑path scanning conflicts with modular design, and spring.factories cannot support the module system well.
3.3 Lack of conditional loading
The configuration in spring.factories is static and cannot decide at runtime whether to load a particular implementation. Although the @Conditional annotation can be used, it is inefficient because all classes are loaded into memory for condition evaluation.
3.4 Configuration scattered across many JARs
In large projects, spring.factories entries are scattered in multiple JARs, making global configuration hard to manage.
3.5 GraalVM native image support
SpringBoot 3.0 aims to provide first‑class support for GraalVM native images. The runtime class‑path scanning used by spring.factories fundamentally conflicts with GraalVM's Ahead‑of‑Time (AOT) compilation model.
Static analysis limitation: GraalVM needs static analysis, but spring.factories scanning is dynamic.
Reflection usage issue: spring.factories relies on reflection, which GraalVM must know ahead of time.
Resource access limitation: Resource file scanning in native images differs from the JVM.
To better support GraalVM, SpringBoot needs a static configuration determined at build time rather than a runtime dynamic scan.
4. Alternative: imports files
4.1 New mechanism introduction
Starting with SpringBoot 3.0, a new imports‑based mechanism replaces spring.factories. These files are placed under META-INF/spring/, with one file per extension‑point type.
4.2 Advantages of the new mechanism
Better performance: Each extension‑point type uses a separate file, avoiding loading unnecessary configuration.
Java module support: Compatible with the Java module system.
Simplified configuration: One fully‑qualified class name per line, no key‑value pairs.
Clearer organization: Configurations are categorized into different files.
4.3 Comparison example
Old way ( spring.factories):
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.FooAutoConfiguration,\
com.example.BarAutoConfigurationNew way ( AutoConfiguration.imports):
com.example.FooAutoConfiguration
com.example.BarAutoConfiguration5. Migration guide
5.1 Auto‑configuration class migration
Move the auto‑configuration classes registered in spring.factories to
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
// Original auto‑configuration class
@Configuration
@ConditionalOnXxx
public class MyAutoConfiguration {
// ...
}
// Add the fully‑qualified name to the imports file
// com.example.MyAutoConfiguration5.2 Migrating other extension points
For other extension‑point types, SpringBoot 3.0 still supports spring.factories, but new projects should use the imports mechanism:
// 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();
}
}5.3 Custom extension‑point migration
Provide a similar imports file for custom extension points:
// Custom extension loader (old)
public class MyExtensionLoader {
public List<MyExtension> loadExtensions() {
return SpringFactoriesLoader.loadFactories(MyExtension.class, null);
}
}
// New way (imports file loading)
public class MyExtensionLoader {
public List<MyExtension> loadExtensions() {
List<String> classNames = SpringFactoriesLoader.loadFactoryNames(MyExtension.class, null);
// ... custom logic
}
}6. Changes in SpringFactoriesLoader
6.1 API changes
In SpringBoot 3.x, SpringFactoriesLoader itself has been updated:
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 files
// ...
}
// ...
}6.2 Compatibility considerations
SpringBoot 3.0 still supports registering some extension points via spring.factories for backward compatibility, but new projects should prefer the imports mechanism.
7. Practical example
7.1 Create custom auto‑configuration
// 1. Configuration properties class
@ConfigurationProperties(prefix = "myapp")
public class MyProperties {
private boolean enabled = true;
private String name = "default";
// getters and setters ...
}
// 2. Auto‑configuration class
@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());
}
}7.2 Register the auto‑configuration
Create the file
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importswith the line:
com.example.MyAutoConfiguration7.3 Project structure
myproject/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/example/
│ │ ├── MyProperties.java
│ │ ├── MyService.java
│ │ ├── MyServiceImpl.java
│ │ └── MyAutoConfiguration.java
│ └── resources/
│ └── META-INF/spring/
│ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── pom.xml8. Performance comparison
Typical medium‑size SpringBoot application startup performance before and after the new mechanism:
Note: actual performance gain depends on project size and structure.
9. FAQ
9.1 Compatibility issues
Question: Existing dependencies still use spring.factories. Will there be compatibility problems?
Answer: SpringBoot 3.0 retains support for spring.factories, so old libraries continue to work. New code should use the imports mechanism.
9.2 Migration difficulty
Question: Migrating a large project to the new mechanism seems heavy.
Answer: Migrate in phases—first move auto‑configuration classes, then other extension points.
9.3 Custom loader migration
Question: How to migrate custom SpringFactoriesLoader users?
Answer: Follow SpringBoot's new implementation and provide a similar imports‑file loading logic.
10. SpringBoot 3.0 and GraalVM integration
10.1 GraalVM overview
GraalVM is a high‑performance JDK that can compile Java applications into native executables ( Native Image). Native images start in milliseconds, use far less memory, and do not require a JVM at runtime.
Fast startup: Millisecond‑level launch time.
Low memory footprint: Suitable for cloud‑native and container environments.
No JVM needed: Runs as a standalone binary.
Ahead‑of‑Time compilation: All code is compiled to machine code at build time.
10.2 Challenges for SpringBoot
SpringBoot's dynamic features clash with GraalVM's static analysis model:
10.3 Compatibility of imports files with GraalVM
Static analyzability: Imports files list all configuration classes, enabling build‑time analysis.
Clear path: Each extension point has a dedicated file, reducing runtime scanning.
Less reflection: Simpler parsing reduces reflection usage.
Build‑time handling: Imports can be processed during AOT compilation to generate metadata.
10.4 SpringBoot AOT engine
// SpringBoot 3.0 AOT processing example
public class SpringAotProcessor {
// 1. Read imports files instead of scanning spring.factories
List<String> configurations = readImportsFiles();
// 2. Evaluate conditions at build time
List<String> effectiveConfigurations = evaluateConditions(configurations, buildTimeProperties);
// 3. Generate proxies at build time
generateProxies(effectiveConfigurations);
// 4. Generate reflection configuration
generateReflectionConfig(effectiveConfigurations);
// 5. Generate resources configuration
generateResourcesConfig();
}10.5 GraalVM integration example
Maven configuration for building a native image:
<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>10.6 Performance comparison: JVM vs GraalVM native image
10.7 Best practices for GraalVM integration
Reduce reflection: Prefer constructor injection over field injection.
Avoid dynamic proxies: Minimize features that require runtime proxies.
Static initialization: Initialize static data at build time.
Use imports files: Register all configuration classes via imports.
Add necessary hints: Use @NativeHint annotations to provide GraalVM with required metadata.
10.8 Limitations and considerations
Dynamic features limited: Runtime bytecode generation and dynamic class loading are restricted.
Reflection usage: Classes used via reflection must be declared explicitly.
Build time: Native image generation takes longer; plan CI/CD accordingly.
Debugging complexity: Debugging native images is more involved than JVM debugging.
Third‑party compatibility: Some libraries may not yet be optimized for GraalVM.
By removing spring.factories and introducing the imports‑based registration, SpringBoot 3.0 significantly improves GraalVM integration, enabling developers to build high‑performance, low‑latency cloud‑native applications more easily.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.
