How Spring Boot auto-configuration works

How Spring Boot auto-configuration works

In this post, we’ll create a basic Spring Boot application that includes the Web dependency. Then, we’ll analyze it to see how we got an embedded Tomcat web server in our application. This way, we’ll get to know how the magic, or better the auto-configuration, works in Spring Boot.

Table of Contents

Creating a basic Spring Boot application

You may use as reference any Spring Boot application with the Web MVC dependency module. If you start from scratch, you can create your own one using the Spring Initialzr at start.spring.io. Make sure you add the ‘Web’ dependency.

Creating a Spring Boot app with Initializr
Creating a Spring Boot app with Initializr

The promise: an embedded Tomcat server

What we get when we add the Web dependency is an independently-deployable web application. Let’s run the application to verify this. When we created the app, we selected Java 14 so that’s the only thing we need. If you prefer to use any other Java version you can do so since that’s not relevant for this guide. Note that the application package includes Maven too.

After unzipping the contents, we execute this command to run the application:

$ ./mvnw spring-boot:run

The logs on console will look similar to these:

2020-04-22 07:25:00.183  INFO 46685 --- [           main] i.t.w.WebAutoconfigurationApplication    : Starting WebAutoconfigurationApplication on moisess-mbp.home with PID 46685 (/Users/moises/dev/ws-blog/web-autoconfiguration/target/classes started by moises in /Users/moises/dev/ws-blog/web-autoconfiguration)
2020-04-22 07:25:00.185  INFO 46685 --- [           main] i.t.w.WebAutoconfigurationApplication    : No active profile set, falling back to default profiles: default
2020-04-22 07:25:00.800  INFO 46685 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-04-22 07:25:00.809  INFO 46685 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-04-22 07:25:00.809  INFO 46685 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.33]
2020-04-22 07:25:00.865  INFO 46685 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-04-22 07:25:00.866  INFO 46685 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 647 ms
2020-04-22 07:25:00.975  INFO 46685 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-04-22 07:25:01.086  INFO 46685 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-04-22 07:25:01.088  INFO 46685 --- [           main] i.t.w.WebAutoconfigurationApplication    : Started WebAutoconfigurationApplication in 1.155 seconds (JVM running for 1.415)
2020-04-22 07:25:17.852  INFO 46685 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-04-22 07:25:17.852  INFO 46685 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-04-22 07:25:17.856  INFO 46685 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 4 ms

A ready-to-use Tomcat embedded server gets started with our Spring Boot application at port 8080 so we could start serving pages, REST APIs, etc. This skeleton application doesn’t bring any predefined web page or API but we can still detect that Tomcat is running if we request any random page at the server location.

Default Tomcat Error Page
Default Tomcat's Error Page

There it is. Tomcat is serving a default error page to indicate that there is no resource at that address. So the server is actually up and running.

We could quickly configure the server to be at a different port by adding this line to the empty application.properties file inside the /src/main/resources folder in this project.

server.port=9090

If we run the application again, we’ll see that now Tomcat will start at port 9090.

2020-04-22 07:50:24.544  INFO 46918 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 9090 (http) with context path ''

You could take it from here and start developing your application. But, if you don’t know how Spring Boot auto-configuration works, this might looks like magic. In fact, Spring Boot is full of these auto-configuration recipes so it’s better to understand the mechanism to be able to customize it to our needs.

Auto-configuration and the Tomcat embedded server

In the Embedded Tomcat case, it all begins with a starter. We added the ‘Web’ dependency to our application so the Spring Boot Web starter got included in our pom.xml file as a maven dependency.

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

    <!--...-->
</dependencies>

Spring Boot provides pre-defined starter packages that are a sort of collections of Spring modules and some third-party libraries and tools together. The spring-boot-starter-web helps you build a standalone web application. It groups the Spring Core Web libraries with Jackson (JSON handling), validation, logging, and the embedded Tomcat server, amongst other tools.

To explain how auto-configuration works, we’ll navigate through the code in the Spring Boot libraries and starter definitions. If you’re familiar with your IDE, you can do it yourself by using typically the Go to implementation shortcuts. Then, choose the option to download sources for the libraries. In any case, I include in this post the most relevant code blocks.

Note also that there are diagrams available at the end of the sections so you can use them as you read. I hope those help to better understand the concepts.

Learn Microservices with Spring Boot - Second Edition

Finding Tomcat

First, let’s navigate from our pom.xml file to the implementation of the starter spring-boot-starter-web. In your IDE, you can normally do this via the artifactId name. We’ll get to the artifact definition, which also includes its own dependencies. If you’re working with a Spring Boot version prior to 2.3, it’ll be a pom.xml file since they used Maven. From that version onwards, you’ll find a Gradle file. See the source code here.

dependencies {
	api(platform(project(":spring-boot-project:spring-boot-dependencies")))
	api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
	api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-json"))
	api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-tomcat"))
	api("org.springframework:spring-web")
	api("org.springframework:spring-webmvc")
}

That file has itself a dependency with spring-boot-starter-tomcat. Let’s now navigate inside that artifact. See the source code of that build.gradle file following this link.

dependencies {
	api(platform(project(":spring-boot-project:spring-boot-dependencies")))
    api("jakarta.annotation:jakarta.annotation-api")
	api("org.apache.tomcat.embed:tomcat-embed-core") {
		exclude group: "org.apache.tomcat", module: "tomcat-annotations-api"
	}
	api("org.glassfish:jakarta.el")
	api("org.apache.tomcat.embed:tomcat-embed-websocket") {
		exclude group: "org.apache.tomcat", module: "tomcat-annotations-api"
	}
}

If we open that one, we see that it contains a non Spring dependency, tomcat-embed-core. This is an Apache library that contains a class Tomcat, used to start an embedded server. This is the link to the latest source file.

Dependency Hierarchy - Finding Tomcat
Dependency Hierarchy - Finding Tomcat

Great! We found the Tomcat embedded implementation. Now, how does Spring uses it automatically?

The Spring Boot Autoconfigure module

The spring-boot-starter-web also depends on the spring-boot-starter, which defines these dependencies (see the source code):

dependencies {
	api(platform(project(":spring-boot-project:spring-boot-dependencies")))
	api(project(":spring-boot-project:spring-boot"))
	api(project(":spring-boot-project:spring-boot-autoconfigure"))
	api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-logging"))
	api("jakarta.annotation:jakarta.annotation-api")
	api("org.springframework:spring-core")
	api("org.yaml:snakeyaml")
}

That’s the core Spring Boot starter and it brings the artifact spring-boot-autoconfigure. That package has a whole bunch of classes annotated with @Configuration which are the reason of a big part of the Spring Boot “magic”.

Learn Microservices with Spring Boot - Second Edition

Web server factory configuration

One of the auto-configuration classes included in the ‘autoconfigure’ module is ServletWebServerFactoryConfiguration. See the full source file if you’re curious. This is the most relevant code block:

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedTomcat {

		@Bean
		TomcatServletWebServerFactory tomcatServletWebServerFactory(
				ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
				ObjectProvider<TomcatContextCustomizer> contextCustomizers,
				ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
			TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
			factory.getTomcatConnectorCustomizers()
					.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getTomcatContextCustomizers()
					.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
			factory.getTomcatProtocolHandlerCustomizers()
					.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
			return factory;
		}

	}
    // ...
}

The line with the @ConditionalOnClass annotation is the key in this article. That annotation instructs Spring that the EmbeddedTomcat configuration should be processed if the Tomcat class is present in the classpath (amongst others that are also included in the tomcat-embed-core artifact). That class is indeed included in the classpath since we already found it before while searching through transitive dependencies.

Therefore, the defined bean will get loaded in the classpath, a TomcatServletWebServerFactory object (source). The factory is contained inside Spring Boot’s core artifact (spring-boot, a dependency that is also included in spring-boot-starter). This class sets up the Tomcat embedded server so this is where the main logic finally lives.

Dependency Hierarchy - Web server Auto-Configuration
Dependency Hierarchy - Web server Auto-Configuration

Customizing server configuration

Similarly, our server.port property gets also loaded via auto-configuration and used in the class ServletWebServerFactoryAutoConfiguration (source). This class loads a conditional bean to customize our Tomcat factory with the loaded server properties or the default ones:

@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
        ServerProperties serverProperties) {
    return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}

SpringBootApplication and Auto-Configuration

The Spring Boot application we generated automatically has a main class annotated with @SpringBootApplication (source). This annotation groups itself a few ones, and @EnableAutoConfiguration is amongst them. With that one we’re (unsurprisingly) enabling auto-configuration.

Spring activates its intelligent mechanism and loads classes from your code and classpath if they are annotated with @Configuration. That’s how we got all the (conditional) beans and their respective behavior described in the previous classes, including the Embedded Tomcat Server.

Spring Boot’s Conditions

As mentioned before, the @ConditionalOnClass annotation plays a key role in auto-configuration. Spring Boot uses all kinds of condition annotations to decide if beans should be loaded in the context. This might enable new features and behaviors in your application. These are some of the variables you can use in conditions:

  • A class is present in the classpath
  • A bean is loaded in the context
  • You’re running a specific Java version range
  • There is a property with a given value
  • There is a resource available in your application

There are many more variations of these conditions, all of them included in the condition package in the autoconfigure module. You can even use aggregated conditions. See the complete list in this link.

It’s important to keep in mind that auto-configuration is not limited to the libraries and modules included with Spring Boot. You can create your own auto-configuration conditions. This is especially useful if you’re building libraries: you can offer to switch some features on and off depending on the application needs or, for instance, you can guarantee backward-compatibility.

Summary and conclusions

Let’s recap. Spring scans and processes all our configuration classes because that’s enabled implicitly via the @SpringBootAnnotation. Given that the condition stated in the EmbeddedTomcat inner static class is fulfilled (Tomcat library is an included dependency), it loads a TomcatServletWebServerFactory bean in the context. This Spring Boot class starts an embedded Tomcat server with the default configuration, exposing an HTTP interface on the port 8080 that we can override using defined key-values in application.properties.

As you can imagine, this same mechanism applies to many other libraries for databases, web servers, message brokers, cloud-native patterns, security, etc. Many of these configuration classes are conditional on the presence of other classes, but there are also other types of conditions like specific property values that we can configure in our properties file.

The concept of auto-configuration is key in Spring Boot. Once you understand it, many features that other developers consider magic are no longer a secret for you. It’s also important to know how Spring Boot works so you can configure it according to your needs, and avoid getting a lot of behavior that you don’t want, or simply don’t need.

This and many other key concepts about writing applications with Spring Boot and creating a complete microservices setup are included in my new book. Check out this page for more information.

Learn Microservices with Spring Boot - Second Edition

I hope this guide was helpful for you and that you learned something new today. Follow me on Twitter for more articles and tips.

Moisés Macero's Picture

About Moisés Macero

Software Developer, Architect, and Author.
Check my workshops

Amsterdam, The Netherlands https://thepracticaldeveloper.com

Comments