Close

Modular auto-configuration in Spring Boot 4

[Last Updated: Nov 9, 2026]

In the previous tutorial, we saw how auto-configuration works. In this tutorial, we will see what changed in Spring Boot 4. The core mechanism is the same, but auto-configuration code is now delivered in a different way: in many small modules instead of one big jar.

This tutorial is for applications and libraries that move from Spring Boot 3.x to Spring Boot 4.x. If you start a new project on Spring Boot 4, you will mostly use the new names directly.


The problem before Spring Boot 4

Up to Spring Boot 3.x, almost all auto-configuration classes lived in a single jar: spring-boot-autoconfigure. When Spring Boot 1.0 was released in 2014, this jar was about 182 KiB. With Spring Boot 3.5, it had grown to about 2 MiB, because Spring Boot supports more and more technologies.

Most applications use only a few of these technologies. But the jar contained code for all of them. This caused some problems:

  • The IDE showed many classes and configuration properties that our application never needs.
  • More auto-configuration classes (and related classes) were on the classpath, so more classes had to be checked at startup.
  • Spring Boot could not know why we added a library to the classpath. For example, if spring-webflux was present, was it for server-side controllers or only for the WebClient? Because of this guess, we sometimes had to turn things off by hand (for example, with setWebApplicationType(WebApplicationType.NONE)).

What changed in Spring Boot 4

Spring Boot 4 splits the big jar into many small, focused modules. Each supported technology now has its own module. Most technologies also have their own starter. Here are the naming rules:

  • Module (jar) names are like spring-boot-<technology>, for example spring-boot-flyway or spring-boot-webmvc.
  • Starter names are like spring-boot-starter-<technology>, for example spring-boot-starter-flyway.
  • The root package of a module starts with org.springframework.boot.<technology>. Its auto-configuration classes are normally in an autoconfigure sub-package.
  • There are matching test modules and test starters, like spring-boot-data-jdbc-test and spring-boot-starter-webmvc-test.

The spring-boot-autoconfigure jar still exists. It keeps the general auto-configuration support, such as @EnableAutoConfiguration, @AutoConfiguration and the org.springframework.boot.autoconfigure.condition annotations. The technology-specific code moved out to the new modules.

Each module registers its own auto-configuration classes with the same .imports file mechanism that we saw in the previous tutorial. So the mechanism itself did not change. Only the place where the classes live changed.

The benefits are:

  • Clearer code: the Spring Boot team can keep each technology separate and clean.
  • Smaller footprint: our application carries only the modules it needs, so there are fewer auto-config classes (and related) on the classpath and less to scan at startup.
  • Better signals: if we use only the spring-boot-webclient module, there is no web server auto-configuration on the classpath at all.
  • New use cases: for example, we can now use Micrometer metrics without the full Actuator.

Having the library on the classpath is not always enough

This is the change that surprises people the most. In Spring Boot 3.x, adding a library was enough. For example, adding flyway-core made Spring Boot run Flyway migrations, because the Flyway auto-configuration was already inside spring-boot-autoconfigure.

In Spring Boot 4, the Flyway auto-configuration is in the spring-boot-flyway module. If that module is not on the classpath, the auto-configuration class is not there, so nothing happens. No error is shown. The migrations just do not run.

The fix is to add the starter for the technology, which brings the module (and the library) for us. A typical Maven change looks like this:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-flyway</artifactId>
</dependency>

The Gradle equivalents:

implementation 'org.springframework.boot:spring-boot-starter-flyway'
implementation("org.springframework.boot:spring-boot-starter-flyway")

The first line is for the Groovy DSL (build.gradle) and the second is for the Kotlin DSL (build.gradle.kts).

If a feature of Spring Boot silently stops working after an upgrade, this is the first thing to check. The Spring 4 migration guide has the full list of starters for each technology. Also, run the application with --debug (see the previous tutorial). A technology whose module is missing will not appear in the conditions report at all, not even under negative matches, because its auto-configuration class is not on the classpath.

Renamed and new starters

Spring Boot 4 also renamed some starters to make names more consistent. The most common example is the web starter:

  • Spring Boot 3.x: spring-boot-starter-web (Spring MVC)
  • Spring Boot 4.x: spring-boot-starter-webmvc

Technologies that had no starter before (for example Flyway) now have one. The idea is simple: every technology has its own starter. Use the starter, not the raw library, when you want Spring Boot to auto-configure it.


Test starters and test annotations

The test support was split in the same way. The old spring-boot-test-autoconfigure jar was divided, and each part moved next to its technology. For example, @AutoConfigureDataJdbc (used to test Spring Data JDBC) is now in the spring-boot-data-jdbc-test module.

For every regular starter, there is now a test starter with the suffix -test. If the application uses spring-boot-starter-webmvc, it should also have spring-boot-starter-webmvc-test in the test scope:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webmvc-test</artifactId>
    <scope>test</scope>
</dependency>
testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
testImplementation("org.springframework.boot:spring-boot-starter-webmvc-test")

The first is for Maven, the second for the Gradle Groovy DSL, and the third for the Gradle Kotlin DSL.


Package changes

Because the code moved to new modules, many classes are now in new packages. Imports in our code may need to change. Here is one example that is related to our previous tutorial. The @EntityScan annotation was in org.springframework.boot.autoconfigure.domain in Spring Boot 3.x. In Spring Boot 4, it is in org.springframework.boot.persistence.autoconfigure.

This matters in two places:

  • Our code: imports of Spring Boot classes, such as customizers and auto-configuration support classes, may need to be updated. The compiler will tell us.
  • Exclusions by name: if we exclude an auto-configuration with excludeName or with the property spring.autoconfigure.exclude, we must use the new fully qualified class name. A wrong name will not be found, and the exclusion will fail or have no effect.

The easiest way to find the new package of a class is to search for the class name in the Spring Boot Javadoc for the version you use.


Classic starters: a helper for migration

Moving everything at once can be a lot of work. To make the upgrade easier, Spring Boot 4 keeps classic starters. They include all the (modular) auto-configuration modules, but not the libraries that those modules need. In practice, they bring back the "everything is available" feeling of Spring Boot 3.x.

The idea is to use them to get the application running, fix the imports, and later change to the specific starters. The dependencies look like this in Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-classic</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test-classic</artifactId>
    <scope>test</scope>
</dependency>

In Gradle (Groovy DSL, then Kotlin DSL):

implementation 'org.springframework.boot:spring-boot-starter-classic'
testImplementation 'org.springframework.boot:spring-boot-starter-test-classic'
implementation("org.springframework.boot:spring-boot-starter-classic")
testImplementation("org.springframework.boot:spring-boot-starter-test-classic")

Classic starters are meant as a temporary step. Over time, replace them with the specific starters that your application really needs, so you get the benefits of the smaller modules.


For authors of custom starters and shared libraries

If you maintain your own starter or a shared library that uses Spring Boot auto-configuration, check these points:

  • If it depended on spring-boot-autoconfigure to use technology-specific classes, depend on the matching new module instead.
  • Update the imports to the new package names.
  • Register your own auto-configuration classes in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports and annotate them with @AutoConfiguration, as explained in the previous tutorial.
  • Do not try to support Spring Boot 3 and Spring Boot 4 in the same artifact. The Spring Boot team strongly discourages it because of the package and module changes. Publish a separate version for each.

Migration checklist

  1. First upgrade to the latest Spring Boot 3.5.x version and fix all deprecation warnings. Deprecated code is removed in Spring Boot 4.
  2. Replace renamed starters (for example, spring-boot-starter-web with spring-boot-starter-webmvc).
  3. Add starters for technologies that you used only through a raw library before (for example Flyway).
  4. Add the matching test starters in the test scope.
  5. Fix imports and fully qualified class names that moved to new packages.
  6. If you get stuck, use the classic starters temporarily, then remove them later.
  7. Run the application with --debug and check the conditions report to confirm that the auto-configurations you expect are applied.

See Also

Join