Demystifying Spring Boot Magic: Starters, Auto-configuration, and Building Your Own Library
Two of its most powerful features of Spring Boot are Starters and Auto-configuration, which have revolutionized Java development by simplifying the process of creating production-ready applications.. These features work behind the scenes, significantly reducing the boilerplate code you need to write. But have you ever wondered how they work?
In this blog, we’ll demystify Spring Boot starters and auto-configuration. We’ll then take it a step further and guide you through building your own Java library, complete with auto-configuration and a custom starter, to experience the magic firsthand.
Part 1: Understanding Spring Boot Starters
Think of Spring Boot Starters as convenient dependency aggregators. Instead of manually adding multiple dependencies to your project, you can simply include a single starter, and it will pull in all the related dependencies you need for a specific type of functionality.
Example:
Let’s say you want to build a web application. Instead of adding dependencies for Spring MVC, Tomcat, Jackson, etc., separately, you can simply add the spring-boot-starter-web
dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
This single starter will bring in all the necessary dependencies for building a web app.
Some other commonly used starters include:
spring-boot-starter-data-jpa
: For working with databases using Spring Data JPA.spring-boot-starter-security
: For securing your application with Spring Security.spring-boot-starter-test
: For testing your application (includes JUnit, Mockito, etc.).
Key benefits of using starters:
- Reduced Dependency Management: You don’t need to hunt down and configure individual libraries in maven (or gradle).
- Consistent Versions: Starters ensure that you’re using compatible versions of dependencies, (unless in your pom, you specify the dependencies which are coming from starter pom and provide a version with that).
- Simplified
pom.xml
orbuild.gradle
: Your build files become cleaner and more readable.
Code of all the starters can be seen here — https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot-starters
Note: As we mentioned in the beginning of this blog that you can create your own starters, similarly some people have created other open source starters which are not part of Springboot
. List of such starters is given on the link given above. Your starter name should not start with Springboot
, as a general guideline.
Part 2: Understanding Spring Boot Auto-configuration
Auto-configuration is the real magic behind Spring Boot’s ease of use. It automatically configures your Spring application based on the dependencies present in your classpath. Based on the presence of some specific classes on the class path, Spring Boot intelligently guesses what you need and sets it up for you. And Springboot starters
also have a role here as they bring those specific libraries and classes on the classpath.
Example:
Let’s say you want to create an application using Springboot, which connects to Cassandra database.
- The starting point: Dependencies Introduced by
spring-boot-starter-data-cassandra
When you include spring-boot-starter-data-cassandra
in your project, it transitively pulls in several key dependencies:
spring-boot-starter
: Core starter, including auto-configuration support, logging, and YAML.spring-data-cassandra
: The core library that provides Spring Data abstractions for Cassandra. This includes:
-CassandraTemplate
for interacting with Cassandra.
- Repository support.
- Mapping annotations.com.datastax.oss:java-driver-core
: The DataStax Java driver, responsible for the low-level communication with your Cassandra cluster.com.datastax.oss:java-driver-mapper-runtime
: Provides an annotation-based mapper (object-graph mapping) to interact with Cassandra.
2. Spring Boot Auto configuration: mechanism explanation
Spring Boot’s auto-configuration mechanism kicks in based on the presence of these dependencies.
Here’s how it works:
@EnableAutoConfiguration
andspring.factories
(The Old Way): In older Spring Boot versions, auto-configuration was enabled using the@EnableAutoConfiguration
annotation. This annotation, usually part of the@SpringBootApplication
annotation, instructed Spring Boot to look for a special file namedspring.factories
located in theMETA-INF
directory of jars on the classpath. Thespring.factories
file contained a list of auto-configuration classes under the keyorg.springframework.boot.autoconfigure.EnableAutoConfiguration
.org.springframework.boot.autoconfigure.AutoConfiguration.imports
(The New Way): Starting from Spring Boot 2.7, a new mechanism was introduced. Auto-configuration classes are now listed in a file namedorg.springframework.boot.autoconfigure.AutoConfiguration.imports
located in theMETA-INF/spring
directory [code is here]. This makes the auto-configuration process more explicit and discoverable. Each line in this file specifies the fully qualified name of an auto-configuration class. If you are building a library that you intend to distribute and reuse across multiple projects, then create theorg.springframework.boot.autoconfigure.AutoConfiguration.imports
file and put that underMETA-INF/spring
directory. Add the fully qualified name of your auto-configuration class on a new line in this file.com.example.myautoconfig.MyAutoConfiguration.
When a user includes your library as a dependency in their Spring Boot application, Spring Boot will discover yourorg.springframework.boot.autoconfigure.AutoConfiguration.imports
file. It will then process your auto-configuration class just like it processes its own, checking conditions and creating beans as defined.- The
spring-boot-autoconfigure
Module: The heart of the auto-configuration mechanism lives in thespring-boot-project/spring-boot-autoconfigure
module [code is here]. This module contains theorg.springframework.boot.autoconfigure.AutoConfiguration.imports
file, which acts as a central registry of all auto-configuration classes provided by Spring Boot itself. Springboot’s own auto-configure packages like for web or Cassandra reside inside this module. As you see that all of those auto-configure packagesauto-configure packages automatically come with Springboot. - You can also use
@Import(MyAutoConfiguration.class)
which will be used by Springboot to load the beans written inMyAutoConfiguration.class
. - Classpath Scanning and Conditional Configuration: When your Spring Boot application starts, it scans the classpath. For each auto-configuration class listed in
org.springframework.boot.autoconfigure.AutoConfiguration.imports
, Spring Boot checks if the conditions specified by that class are met. This is where conditional annotations come into play.
3. The @AutoConfiguration
annotation
Introduced in Spring Boot 2.7 as a replacement for listing auto-configurations in spring.factories
under the EnableAutoConfiguration
key, the @AutoConfiguration
annotation serves the following purposes:
- Marker Annotation: It acts as a marker to indicate that a class is an auto-configuration class. While not strictly mandatory for the auto-configuration process to work (as the class would still be picked up from the imports file), it provides a clear and explicit way to identify auto-configuration classes within your codebase.
- Metadata for Ordering:
@AutoConfiguration
can be used in conjunction with@AutoConfigureBefore
and@AutoConfigureAfter
to specify the order in which auto-configuration classes should be applied. This is important for ensuring that dependencies between auto-configurations are resolved correctly. For example, if auto-configuration class A depends on a bean created by auto-configuration class B, then B should be configured before A.
How @AutoConfiguration
Relates to org.springframework.boot.autoconfigure.AutoConfiguration.imports
- The
imports
File is the Source of Truth: Theorg.springframework.boot.autoconfigure.AutoConfiguration.imports
file is the primary mechanism for Spring Boot to discover auto-configuration classes. Any class listed in this file will be considered for auto-configuration, regardless of whether it's annotated with@AutoConfiguration
or not. @AutoConfiguration
is for Clarity and Ordering: The@AutoConfiguration
annotation adds clarity and structure to your codebase. It's a best practice to annotate your auto-configuration classes with it. Furthermore, when you need to define a specific order of execution between auto-configuration classes@AutoConfiguration
becomes important.
Example Usage:
@AutoConfiguration
@ConditionalOnClass(CassandraOperations.class)
@EnableConfigurationProperties(CassandraProperties.class)
@Import({ CassandraDataConfiguration.class, ThrottlingCassandraAutoConfiguration.class })
public class CassandraDataAutoConfiguration {
//...
}
Key Differences from the Old spring.factories
Approach
- Explicit vs. Implicit: The
org.springframework.boot.autoconfigure.AutoConfiguration.imports
approach makes auto-configuration more explicit. You can see all the auto-configuration classes that are in play by looking at this file. Withspring.factories
, it was less obvious which auto-configurations were being applied. - Discoverability: The new approach improves discoverability. It’s easier to find and understand the available auto-configurations.
- Ordering: While ordering could be controlled using
@AutoConfigureBefore
and@AutoConfigureAfter
with the old approach as well, using these annotations along with@AutoConfiguration
provides a more dedicated and standardized mechanism for ordering.
4. Auto-configuration Classes: The Building Blocks
All the auto configuration classes use @AutoConfiguration
annotation (Code is here). And @AutoConfiguration
annotation class uses @Configuration
annotation. Which makes an auto-configuration class a regular Java class annotated with @Configuration
. This signals to Spring that it should be processed as a source of bean definitions. However, what makes it an "auto-configuration" class are the special conditional annotations that control its behavior.
The primary auto-configuration classes involved here are:
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration
:
This class is the heart of Cassandra auto-configuration. It is responsible for setting up the core beans needed to work with Cassandra in a Spring Boot application. It imports other auto-configuration classes for specific functionalities. [Code is here]
- @Configuration
: Marks it as a configuration class.
- @ConditionalOnClass({ CqlSession.class, CassandraTemplate.class })
: This is crucial. It tells Spring Boot to only process this class if both CqlSession
(from the DataStax driver) and CassandraTemplate
(from Spring Data Cassandra) are present on the classpath. This condition is met because spring-boot-starter-data-cassandra
brings in these dependencies.
- @EnableConfigurationProperties(CassandraProperties.class)
: This annotation enables the use of configuration properties defined in the CassandraProperties
class. We'll discuss properties in detail later.
- @Import(...)
: This annotation is used to import other configuration classes like CassandraDataConfiguration
which has the default configuration for Cassandra and ThrottlingCassandraAutoConfiguration
which provides default configuration related to request throttling.
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
:
This class is responsible for configuring a CqlSession
using the DataStax Java driver. [Code is here]
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration
:
Enables auto-configuration of Spring Data Cassandra Repositories. [Code is here]
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration
:
This class is responsible for setting up the core beans needed to work with Cassandra in a Spring Boot Reactive application. [Code is here]
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration
:
Enables auto-configuration of Spring Data Cassandra Reactive Repositories. [Code is here]
5. Conditional Annotations: The Gatekeepers
Conditional annotations are the heart of auto-configuration. They determine whether a particular auto-configuration class or a specific bean definition within that class should be applied. Here are some of the most important ones, with examples from Cassandra’s auto-configuration:
@ConditionalOnClass
:
- Example:
@ConditionalOnClass({ CqlSession.class, CassandraTemplate.class })
(onCassandraDataAutoConfiguration
) - Purpose: Ensures that the configuration is applied only if the specified classes are present on the classpath.
@ConditionalOnMissingBean
:
- Example:
@ConditionalOnMissingBean(CassandraTemplate.class)
(on thecassandraTemplate
bean method withinCassandraDataAutoConfiguration
) - Purpose: Creates a bean only if no other bean of the same type is already defined in the application context. This allows you to override Spring Boot’s default beans.
@ConditionalOnProperty
:
- Example:
@ConditionalOnProperty(prefix = "spring.data.cassandra", name = "local-datacenter")
(on various bean methods inCassandraAutoConfiguration
) - Purpose: Applies the configuration only if a specific property is set (or not set) in your
application.properties
orapplication.yml
.
@ConditionalOnSingleCandidate
:
- Example:
@ConditionalOnSingleCandidate(CassandraConverter.class)
(on thecassandraTemplate
bean method withinCassandraDataAutoConfiguration
) - Purpose: Creates a bean only if there is exactly one bean of the specified type in the application context.
@ConditionalOnBean
:
- Example:
@ConditionalOnBean(CqlSession.class)
(on thecassandraSession
bean method withinCassandraDataConfiguration
) - Purpose: Creates a bean only if a bean of the specified type is already defined in the application context.
@AutoConfigureAfter
:
- Example:
@AutoConfigureAfter(CassandraAutoConfiguration.class)
(onCassandraDataAutoConfiguration
) - Purpose: Specifies that this auto-configuration should be applied after another auto-configuration class. This helps to control the order of configuration.
@AutoConfigureBefore
:
- Example:
@AutoConfigureBefore(CassandraReactiveDataAutoConfiguration.class)
(onCassandraDataAutoConfiguration
) - Purpose: Specifies that this auto-configuration should be applied before another auto-configuration class.
6. Configuration Properties: Externalized Settings
Auto-configuration often relies on externalized configuration properties to customize its behavior. For example the url and port of Cassandra database server. These properties are typically defined in your application.properties
or application.yml
file.
@ConfigurationProperties
: This annotation is used to bind properties to a Java class. For Cassandra, Spring Boot provides theorg.springframework.boot.autoconfigure.cassandra.CassandraProperties
class. [Code is here]@EnableConfigurationProperties
: This annotation, used in auto-configuration classes, enables the processing of@ConfigurationProperties
classes. [Look here]
Example: CassandraProperties
The CassandraProperties
class is annotated with @ConfigurationProperties(prefix = "spring.data.cassandra")
. This means that any property in your application.properties
that starts with spring.data.cassandra
will be mapped to a field in this class.
@ConfigurationProperties(prefix = "spring.data.cassandra")
public class CassandraProperties {
private String keyspaceName;
private String contactPoints = "localhost"; // Default value
private int port = 9042; // Default value
private String localDatacenter;
// ... getters and setters ...
}
In your application.properties
, you can then set:
spring.data.cassandra.keyspace-name=mykeyspace
spring.data.cassandra.contact-points=cassandra1.example.com,cassandra2.example.com
spring.data.cassandra.local-datacenter=datacenter1
7. How Auto-Configuration Uses Properties
Auto-configuration classes inject the CassandraProperties
bean and use its values to configure other beans.
Example:
In CassandraAutoConfiguration
, the CqlSession
bean is configured using values from CassandraProperties
:
@Bean
@ConditionalOnMissingBean
public CqlSession cqlSession(CassandraProperties properties) {
CqlSessionBuilder builder = CqlSession.builder();
// ... many other configurations based on properties ...
if (StringUtils.hasText(properties.getLocalDatacenter())) {
builder.withLocalDatacenter(properties.getLocalDatacenter());
}
return builder.build();
}
8. Putting It All Together: The Cassandra Example
- You add
spring-boot-starter-data-cassandra
. - Spring Boot finds
org.springframework.boot.autoconfigure.AutoConfiguration.imports
inspring-boot-autoconfigure.jar
. - It sees
CassandraDataAutoConfiguration
listed there. @ConditionalOnClass
onCassandraDataAutoConfiguration
passes because the starter brought in the necessary dependencies.@EnableConfigurationProperties(CassandraProperties.class)
is processed, makingCassandraProperties
available.- Properties from your
application.properties
(or defaults) are bound toCassandraProperties
. CassandraAutoConfiguration
creates aCqlSession
bean, potentially using values fromCassandraProperties
.CassandraDataAutoConfiguration
createsCassandraTemplate
,CassandraConverter
, etc., beans, also potentially using values fromCassandraProperties
and theCqlSession
bean. The@ConditionalOnMissingBean
checks allow you to override these beans if needed.- Other auto-configuration classes (like
CassandraRepositoriesAutoConfiguration
if you use repositories) are processed similarly.
Part 3: Building Your Own Auto-configurable Library
Now, let’s get our hands dirty and build our own library that can be auto-configured by Spring Boot.
Scenario:
Let’s create a simple library that provides a “Greeting Service.” This service will have a method to generate a personalized greeting message.
Project Structure:
We will create 2 projects:
my-greeting-library
: Our library that contains the GreetingServicemy-greeting-starter
: Our starter that handles the autoconfiguration of the beans in our library.
Step 1: Create the my-greeting-library
project
- Create a new Maven or Gradle project.
- Add the following dependency in
pom.xml
(orbuild.gradle
):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>3.1.3</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-greeting-library</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
- Create
GreetingAutoConfiguration
class:
// GreetingAutoConfiguration.java
package com.example.starter;
import com.example.mylibrary.DefaultGreetingService;
import com.example.mylibrary.GreetingProperties;
import com.example.mylibrary.GreetingService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass(GreetingService.class)
@EnableConfigurationProperties(GreetingProperties.class)
public class GreetingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public GreetingService greetingService(GreetingProperties greetingProperties) {
return new DefaultGreetingService(greetingProperties);
}
}
Explanation:
@Configuration
: Marks this class as a source of bean definitions.@ConditionalOnClass(GreetingService.class)
: This configuration will only be applied if theGreetingService
class is present on the classpath (meaning our library is included).@EnableConfigurationProperties(GreetingProperties.class)
: Enables binding of properties fromapplication.properties
orapplication.yml
to theGreetingProperties
bean, allowing external configuration.@Bean
: Defines thegreetingService
bean.@ConditionalOnMissingBean
: This bean will only be created if there isn't already a bean of typeGreetingService
defined in the application context. This allows users to override our default implementation if they want.
Step 3: Create spring.factories
file
- In your
my-greeting-starter
project, create a file namedspring.factories
undersrc/main/resources/META-INF/
. - Add the following content to
spring.factories
:
org.springframework.boot.autoconfigure.AutoConfiguration.imports =
com.example.starter.GreetingAutoConfiguration
Step 4: Create the spring-autoconfigure-metadata.properties
file
- In your
my-greeting-starter
project, create a file namedspring-autoconfigure-metadata.properties
undersrc/main/resources/META-INF/
. - Add the following content to
spring-autoconfigure-metadata.properties
:
# Auto Configure
com.example.starter.GreetingAutoConfiguration.ConditionalOnClass =
com.example.mylibrary.GreetingService
Step 5: Build the starter and install both packages to your local repository
- Build the starter. You should have the starter jar in your target directory.
- Use
mvn install
(or equivalent gradle command) to install bothmy-greeting-library
andmy-greeting-starter
to your local Maven repository.
Part 4: Using Your Custom Starter
Now, let’s create a new Spring Boot application and use our custom starter.
Step 1: Create a new Spring Boot project
- Use Spring Initializr or your IDE to create a new project.
- Add our custom starter as a dependency:
<dependency>
<groupId>com.example</groupId>
<artifactId>my-greeting-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
Step 2: Create a REST Controller (optional)
Let’s create a simple REST controller to test our greeting service:
// GreetingController.java
package com.example.demo;
import com.example.mylibrary.GreetingService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetingController {
private final GreetingService greetingService;
public GreetingController(GreetingService greetingService) {
this.greetingService = greetingService;
}
@GetMapping("/greet")
public String greet(@RequestParam(value = "name", defaultValue = "World") String name) {
return greetingService.greet(name);
}
}
Step 3: Configure application.properties
(optional)
You can customize the greeting message in application.properties
or application.yml
:
greeting.message=Howdy, %s!
Step 4: Run the Application
Run your Spring Boot application. If you access http://localhost:8080/greet?name=John
, you should see the output: Howdy, John!
(or Hello, John!
if you didn't customize the message).
Conclusion:
Congratulations! You’ve successfully created your own auto-configurable library and custom starter in Spring Boot. You’ve learned how Spring Boot starters simplify dependency management, how auto-configuration magically configures your application, and how to build your own reusable components that integrate seamlessly with the Spring Boot ecosystem.
This knowledge opens up a whole new level of possibilities for building modular, maintainable, and easily configurable Spring Boot applications. You can now create libraries that can be effortlessly reused across multiple projects, reducing development time and promoting consistency. Keep exploring, keep building, and enjoy the power of Spring Boot!