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.

macrozheng
macrozheng
macrozheng
Why Spring Boot 3 Removed spring.factories & How to Migrate to imports

1. Introduction

Spring Boot 3.0 introduces a major change by removing the spring.factories file, 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.factories file 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.factories

is 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, SpringFactoriesLoader scans all JARs for META-INF/spring.factories, reads the entries and loads the corresponding classes, following a “convention over configuration” approach.

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
        // ...
    }
}

3. Why spring.factories was removed

3.1 Performance issues

Scanning every JAR for spring.factories becomes 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.factories unsuitable 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.factories relies 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 imports file 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.factories entry:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.FooAutoConfiguration,\
com.example.BarAutoConfiguration

New AutoConfiguration.imports entry:

com.example.FooAutoConfiguration
com.example.BarAutoConfiguration

5. Migration guide

5.1 Auto‑configuration classes

Move entries from 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.MyAutoConfiguration

5.2 Other extension points

For non‑auto‑configuration extension points, keep using spring.factories if necessary, but prefer the new registration style in new projects.

// 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 loaders

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
    }
}

6. Changes to SpringFactoriesLoader

6.1 API updates

The old loadFactories method is deprecated; the new loadFactoryNames reads the imports files directly.

6.2 Compatibility

Spring Boot 3.0 still supports spring.factories for backward compatibility, but new projects should adopt the imports mechanism.

7. Practical example

7.1 Custom auto‑configuration

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

7.2 Register the auto‑configuration

Create

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

containing:

com.example.MyAutoConfiguration

7.3 Project structure

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

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.factories continue 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.loadFactoryNames pattern.

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

public class SpringAotProcessor {
    public void process() {
        List<String> configurations = readImportsFiles();
        List<String> effectiveConfigurations = evaluateConditions(configurations, buildTimeProperties);
        generateProxies(effectiveConfigurations);
        generateReflectionConfig(effectiveConfigurations);
        generateResourcesConfig();
    }
}

10.4 GraalVM native image configuration

Example Maven setup for building a native image with Spring Native and the new imports mechanism.

<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.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 @NativeHint to 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.factories and 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

migrationperformanceSpring BootgraalvmimportsSpring Factories
macrozheng
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.