Backend Development 18 min read

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.

<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.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:

<code>org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.FooAutoConfiguration,\
com.example.BarAutoConfiguration
</code>

New

AutoConfiguration.imports

entry:

<code>com.example.FooAutoConfiguration
com.example.BarAutoConfiguration
</code>

5. Migration guide

5.1 Auto‑configuration classes

Move entries from

spring.factories

to

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.factories

if 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

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

<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.imports

containing:

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

<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>&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.experimental&lt;/groupId&gt;
        &lt;artifactId&gt;spring-native&lt;/artifactId&gt;
        &lt;version&gt;${spring-native.version}&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;image&gt;
                    &lt;builder&gt;paketobuildpacks/builder:tiny&lt;/builder&gt;
                    &lt;env&gt;
                        &lt;BP_NATIVE_IMAGE&gt;true&lt;/BP_NATIVE_IMAGE&gt;
                    &lt;/env&gt;
                &lt;/image&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.springframework.experimental&lt;/groupId&gt;
            &lt;artifactId&gt;spring-aot-maven-plugin&lt;/artifactId&gt;
            &lt;executions&gt;
                &lt;execution&gt;
                    &lt;id&gt;generate&lt;/id&gt;
                    &lt;goals&gt;
                        &lt;goal&gt;generate&lt;/goal&gt;
                    &lt;/goals&gt;
                &lt;/execution&gt;
            &lt;/executions&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;
</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

@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.

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

login 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.