How Spring Boot auto-configuration works

How Spring Boot auto-configuration works

In this post, you’ll learn how the magic, or better said the auto-configuration, works in Spring Boot. To do so, you’ll analyze how a basic Spring Boot app works to see how you get an embedded Tomcat web server in an example codebase available on Github.

Table of Contents

Creating a basic Spring Boot application

You may use as a 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.

Figure 1. Creating a Spring Boot app with Initializr

The magic: 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 prerequisite we need to install on our computer. 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, so you don’t need to install this tool.

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

$ ./mvnw spring-boot:run

The logs will output something similar to these lines:

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

Listing 1. Application logs showing the embedded Tomcat initialization

A ready-to-use Tomcat embedded server gets started with our Spring Boot application at port 8080 so we could start serving static HTML pages, REST APIs, etc. This skeleton application doesn’t bring any predefined web page or API, but we can still verify that Tomcat is running if we request any random page at the server location. For example, we could navigate to http://localhost:8080/hola in our browser and get the error page shown below.

Figure 2. 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. Therefore, the server is 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

Then, 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. You just accept that the ‘magic’ works. However, if you want to be in control of the tools you’re using, and really understand how you can get the best of it, you better learn the Spring Boot basics. Not only you’ll be able to adapt its behavior when needed, but you’ll also know how to find your way through the tools in case of bugs or errors.

The embedded Tomcat server is just an example, but it’s a good one to understand how the autoconfiguration mechanism does its job. Let’s dive into it, so you can stop using the word ‘magic’ to describe how Spring Boot works internally.

Spring Boot Starters and Auto-configuration

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>

Listing 2. The spring-boot-starter-web in our pom.xml file

Spring Boot provides pre-defined starter packages, which are 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. Those diagrams will help you understand the concepts better.

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 before 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")
}

Listing 3. Spring Boot Starter Web’s build configuration (build.gradle)

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

Listing 4. Spring Boot Starter Tomcat’s build configuration (build.gradle)

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.

Figure 3. Dependency Hierarchy - Tomcat Auto-Configuration

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

Listing 5. Spring Boot Starter’s build configuration (build.gradle)

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

	}
    // ...
}

Listing 6. ServletWebServerFactoryConfiguration.java inside ‘spring-boot-autoconfigure’

The line with the @ConditionalOnClass annotation is the key concept 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.

Figure 4. Dependency Hierarchy - Web server Auto-Configuration (complete)

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

Listing 7. ServletWebServerFactoryAutoConfiguration.java inside ‘spring-boot-autoconfigure’

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

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 variations 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 at 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 automatically by checking the project’s structure.

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 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.
Are you interested in my workshops?

Málaga, Spain https://thepracticaldeveloper.com

Comments