Backend Development 21 min read

Why SpringBoot 3.0 Dropped spring.factories and What to Use Instead

SpringBoot 3.0 removes the long‑standing spring.factories file due to performance, modularity, and GraalVM native image challenges, introduces a new imports‑file mechanism, and provides detailed migration steps, code examples, performance comparisons, and best practices for GraalVM integration.

Architect
Architect
Architect
Why SpringBoot 3.0 Dropped spring.factories and What to Use Instead

1. Introduction

During the evolution of SpringBoot, version 3.0 removed the long‑standing

spring.factories

file, which was the core of auto‑configuration and extension mechanisms. Engineers need to understand the new mechanism and migration strategy.

2. What is spring.factories

Before discussing its removal, we first need to understand the role of the

spring.factories

file in SpringBoot.

2.1 Basic concept

spring.factories

is a configuration file located under

META-INF/

that uses 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 the following main uses:

2.3 Working principle

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 enables “convention over configuration” auto‑configuration.

<code>// SpringFactoriesLoader core code example (SpringBoot 2.x)
public final class SpringFactoriesLoader {
    // ...
    public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
        // ...
        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 classpath
        // ...
    }
    // ...
}
</code>

3. Why spring.factories was removed

The SpringBoot team decided to drop the spring.factories mechanism for several key reasons:

3.1 Performance issues

The mechanism scans all JARs 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), the class‑path scanning approach conflicts with modular design, and spring.factories cannot support it well.

3.3 Lack of conditional loading

Configurations in spring.factories are static; although @Conditional can be used, it is inefficient because all classes are loaded into memory for condition evaluation.

3.4 Configuration dispersion

In large projects, spring.factories configurations are scattered across many JARs, making global management difficult.

3.5 GraalVM native image support

SpringBoot 3.0 aims to provide first‑class GraalVM native image support. The runtime class‑path scanning of spring.factories fundamentally conflicts with GraalVM’s ahead‑of‑time compilation model.

Static analysis limitation: GraalVM needs static analysis, but spring.factories scanning is dynamic.

Reflection usage issue: spring.factories relies on reflection, which requires explicit configuration for native images.

Resource access limitation: Resource file scanning differs in native images and needs special handling.

Therefore a static, build‑time configuration approach is required.

4. Alternative: imports files

4.1 New mechanism introduction

Starting with SpringBoot 3.0, a new imports‑file 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 configurations.

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 grouped by functionality.

4.3 Example comparison

Old way (spring.factories):

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

New way (AutoConfiguration.imports):

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

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

:

<code>// Original auto‑configuration class
@Configuration
@ConditionalOnXxx
public class MyAutoConfiguration {
    // ...
}
</code>
<code>// In META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.MyAutoConfiguration
</code>

5.2 Migration of other extension points

For other extension points, spring.factories is still supported, but new projects should use the imports mechanism. Example for ApplicationListener:

<code>// Before SpringBoot 3.0 (spring.factories)
org.springframework.context.ApplicationListener=com.example.MyListener

// After SpringBoot 3.0 (bean registration)
@Configuration
public class MyConfiguration {
    @Bean
    public MyListener myListener() {
        return new MyListener();
    }
}
</code>

5.3 Custom extension point migration

Provide a similar imports file for custom extension points:

<code>// Custom extension loader (old)
public class MyExtensionLoader {
    public List<MyExtension> loadExtensions() {
        return SpringFactoriesLoader.loadFactories(MyExtension.class, null);
    }
}

// New approach
public class MyExtensionLoader {
    public List<MyExtension> loadExtensions() {
        List<String> classNames = SpringFactoriesLoader.loadFactoryNames(MyExtension.class, null);
        // custom imports handling
    }
}
</code>

6. Changes in SpringFactoriesLoader

6.1 API changes

In SpringBoot 3.0, SpringFactoriesLoader has been refactored:

<code>// Deprecated method
@Deprecated
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) { ... }

// New method
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ... }
</code>

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

Complete example of a custom auto‑configuration in SpringBoot 3.0:

<code>// 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());
    }
}
</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
│       └── resources/
│           └── META-INF/
│               └── spring/
│                   └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── pom.xml
</code>

8. Performance comparison

Typical medium‑size SpringBoot application startup performance after switching to the new mechanism:

Note: actual improvement depends on project size and structure.

9. FAQ and solutions

9.1 Compatibility issues

Question: Existing dependencies still use spring.factories; will there be compatibility problems?

Solution: SpringBoot 3.0 retains support for spring.factories, so old libraries work, but new code should use the imports mechanism.

9.2 Migration effort

Question: Large projects have a heavy migration workload.

Solution: Migrate in phases, starting with auto‑configuration classes, then other extension points.

9.3 Custom loader migration

Question: How to migrate custom SpringFactoriesLoader users?

Solution: Follow SpringBoot’s new implementation and provide a similar imports‑file loading mechanism.

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) with fast startup, low memory usage, and no JVM requirement.

Fast startup: Millisecond‑level launch time.

Low memory footprint: Suitable for cloud‑native and container environments.

No JVM: Runs independently of a Java runtime.

Ahead‑of‑time compilation: All code is compiled to machine code at build time.

10.2 Challenges for GraalVM support

SpringBoot’s dynamic features conflict with GraalVM’s static analysis model.

10.3 Compatibility of imports files

The imports mechanism solves key GraalVM integration problems:

Static analyzability: All configuration classes are listed explicitly for build‑time analysis.

Clear paths: Each extension‑point has a specific file path, reducing runtime scanning.

Less reflection: Simpler parsing reduces reliance on reflection.

Build‑time processing: Imports can be processed during AOT compilation to generate metadata.

10.4 SpringBoot AOT engine

SpringBoot 3.0 introduces an AOT engine that performs the following at build time:

<code>public final 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 statically
    generateProxies(effectiveConfigurations);
    // 4. Generate reflection configuration
    generateReflectionConfig(effectiveConfigurations);
    // 5. Generate resource configuration
    generateResourcesConfig();
}
</code>

10.5 GraalVM integration example

Maven configuration for a native image build:

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

Old spring.factories registration:

<code># META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.MyNativeCompatibleConfig
</code>

New imports registration:

<code># META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.MyNativeCompatibleConfig
</code>

Native‑compatible auto‑configuration:

<code>@AutoConfiguration
@NativeHint(options = "--enable-url-protocols=http")
public class MyNativeCompatibleConfig {
    @Bean
    public MyService myService() {
        return new MyNativeCompatibleService();
    }
}
</code>

10.6 Performance: JVM vs native image

After using the imports mechanism, SpringBoot applications show the following performance in GraalVM native images:

10.7 Best practices for GraalVM integration

Reduce reflection usage: Prefer constructor 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 hints.

10.8 Limitations and considerations

Dynamic features limited: Runtime bytecode generation and class loading are restricted.

Reflection declarations required: All reflective accesses must be declared.

Long build times: Native image builds take longer, requiring CI/CD planning.

Debugging complexity: Debugging native images is more difficult than JVM.

Third‑party library compatibility: Some dependencies may not be GraalVM‑ready.

By removing spring.factories and adopting the imports mechanism, SpringBoot 3.0 greatly improves GraalVM integration, enabling developers to build high‑performance, low‑latency cloud‑native applications.

migrationSpringBootGraalVMauto‑configurationimportsspring.factories
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.