Skip to main content
  1. Posts/

Spring Boot Starters

Spring Boot Starters in a Nutshell #

Spring Boot Starters are a set of convenient dependency descriptors that you can include in your project to simplify the Maven or Gradle configuration process. Essentially, starters are pre-configured bundles of dependencies that are designed to provide the necessary libraries to achieve a specific goal within a Spring application. They help in getting a Spring application up and running as quickly as possible by reducing the need for specifying individual dependencies and ensuring version compatibility among them.

Key Features of Spring Boot Starters #

  • Simplify Dependency Management: Starters include a collection of dependencies that work well together and are tested to ensure compatibility. This means you don’t have to hunt down the correct versions of libraries that are compatible with each other.

  • Rapid Application Development: By providing a quick way to include dependencies, starters reduce the boilerplate code and configuration you need to start a project, enabling you to focus on writing your application logic.

  • Customization and Extensibility: While starters provide a basic set of dependencies, they are designed to be easily customizable and extensible, allowing you to add or override dependencies as needed for your specific use case.

  • Wide Range of Application Types: There is a starter for almost every type of application or service that you might want to create with Spring, including web applications, data access applications, messaging services, and more. Examples include:

    • spring-boot-starter-web for building web applications using Spring MVC.
    • spring-boot-starter-data-jpa for using Spring Data JPA with Hibernate.
    • spring-boot-starter-security for adding Spring Security to an application.
    • spring-boot-starter-test for testing Spring Boot applications with libraries like JUnit, Hamcrest, and Mockito.

How to Use a Starter #

To use a Spring Boot Starter, you simply include it in your project’s build configuration file (pom.xml for Maven or build.gradle for Gradle). For example, to include the Spring Boot Starter Web in a Maven project, you would add the following dependency:

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

This inclusion automatically configures your application to use Spring MVC for web development, along with providing default configurations for a Tomcat web server, JSON converters, and more, all without needing to explicitly define these dependencies and their compatible versions.

Starters reflect the “opinionated” approach of Spring Boot, which advocates for convention over configuration to reduce development effort and increase productivity. They’re a foundational part of the Spring Boot philosophy, aiming to make it easier to build production-ready applications quickly.

Anatomy of Spring Boot Starter #

The anatomy of a Spring Boot Starter encompasses several key components that work together to simplify the configuration and setup of Spring applications. Starters are designed to bundle the necessary dependencies, auto-configuration code, and property files needed to get an application running with minimal setup. Here’s a breakdown of the main components involved:

1. Dependency Descriptors #

Dependency descriptors are the core of a Spring Boot Starter. They are defined in the build configuration file of the Starter (e.g., pom.xml for Maven projects or build.gradle for Gradle projects). These descriptors list all the libraries (dependencies) that are included in the Starter. When you add a Starter to your project, Maven or Gradle automatically resolves and downloads these dependencies and makes them available in your project.

For example, the spring-boot-starter-web includes dependencies for Spring MVC, Tomcat, and JSON processing libraries among others, bundling everything needed to develop a web application.

2. Auto-configuration Classes #

Spring Boot uses a concept called auto-configuration to automatically configure your Spring application based on the libraries present on your classpath. This is facilitated by the @EnableAutoConfiguration annotation or by including the spring-boot-starter-autoconfigure dependency directly or transitively through other starters.

Auto-configuration classes in Spring Boot are conditionally loaded based on certain criteria. For example, if the Spring MVC library is in the classpath, Spring Boot automatically configures your application to be a web application. These auto-configuration classes use the @Conditional annotations (like @ConditionalOnClass, @ConditionalOnBean, @ConditionalOnMissingBean, etc.) to decide when to apply certain configurations.

3. Configuration Properties Files #

Starter packages also include configuration properties files, which are used to customize the auto-configuration behavior. Properties files (typically application.properties or application.yml located in the src/main/resources directory) allow developers to specify parameters that control the behavior of the auto-configured beans. Spring Boot applications can have multiple properties files, including profile-specific ones (like application-dev.properties for development environments).

These properties cover a wide range of configurations, such as server port, database URLs, security settings, and more. Spring Boot provides sensible defaults for many settings, but you can easily override them in your properties files.

4. Property Files #

Beyond the application-level properties files, starters themselves might include additional property files that define default values for certain properties related to the specific capabilities they provide. These defaults can be overridden by the application developer in their own application.properties or application.yml files.

For example, the spring-boot-starter-data-jpa might include defaults for database-related properties, but developers can override these to configure the database connection according to their specific needs.

Building Your Own Custom Spring Boot Starter #

Please refer official documentation

Let’s imagine that we are working for Acme corp. and we want to unify logging and base dependencies configuration for all Spring Boot microservices across the Organization. To achieve this, let’s create a spring boot starter which configures opinionated JSON logging for your application. This might be required if you have fluentd agent or another logs collector.

Creating a module #

Let’s start with the naming convention. As a reminder, naming is very hard problem in computer science :)

Do not start your module names with “spring-boot”, even if you use a different Maven groupId. “spring-boot” prefix is reserved for Spring Boot project. You may name give prefix after your organization name, e.g.: acme-spring-boot-starter-something or acme-something-spring-boot-starter (e.g.) or project-prefix-something (e.g.). Have a look at another Spring Boot projects for inspiration.

You may put your functionality into spring boot starter module, or you may “package” another artifact into spring boot starter, just adding it as a dependency to the starter along with extra optional configuration files.

As a rule of thumb, you should name a combined module after the starter. For example, assume that you are creating a starter for “acme” and that you name the auto-configure module acme-spring-boot and the starter acme-spring-boot-starter. If you only have one module that combines the two, name it acme-spring-boot-starter.

In our case, let’s name our starters with acme-spring-boot-starter- to reflect our organization name. Let’s name our starter acme-spring-boot-starter-base for brevity. For real life scenarios consider creating more fine-grained starters, e.g. acme-spring-boot-starter-logging, acme-spring-boot-starter-observability etc

Let’s create a pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.acme.springboot</groupId>
    <artifactId>acme-spring-boot-starter-base</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>Base SpringBoot Starter for Acme Inc. microservices</name>
    <description>
        It contains logging configuration, and standard dependencies
    </description>

    <properties>
        <java.version>21</java.version>
        <spring-boot.repackage.skip>true</spring-boot.repackage.skip>
        <logstash-logback-encoder.version>7.4</logstash-logback-encoder.version>
        <netty.version>4.1.108.Final</netty.version>
        <kotlin.version>1.9.23</kotlin.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-bom</artifactId>
                <version>${kotlin.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-kotlin</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor.kotlin</groupId>
            <artifactId>reactor-kotlin-extensions</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-reflect</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlinx</groupId>
            <artifactId>kotlinx-coroutines-reactor</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>net.logstash.logback</groupId>
            <artifactId>logstash-logback-encoder</artifactId>
            <version>${logstash-logback-encoder.version}</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>compile</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <args>
                        <arg>-Xjsr305=strict</arg>
                    </args>
                    <languageVersion>2.0</languageVersion>
                    <compilerPlugins>
                        <plugin>spring</plugin>
                    </compilerPlugins>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-allopen</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

</project>

The starter incorporates essential dependencies for webflux, validation, actuator autoconfiguration, aimed at standardizing and simplifying the setup of Acme’s microservices. Those dependencies will be automatically included in the dependent projects

To use this new module in other project we just need to declare a dependency in pom.xml (example):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.acme.springboot.chassis</groupId>
    <artifactId>sample</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBoot3 Sample App</name>
    <description>Petstore Sample App</description>

    <dependencies>
        <dependency>
            <groupId>com.acme.springboot</groupId>
            <artifactId>acme-spring-boot-starter-base</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

Note, that we are not inheriting from the spring boot starter. We are just adding a dependency.

And application code will look like this:

package com.acme.springboot.sample

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args)
}

Loading Configurations Automatically #

Spring Boot checks for the presence of a META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports file within your published jar. The file should list your configuration classes, with one class name per line, as shown in the following example:

com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration

Read more about locating auto-configuration candidates here.

Let’s start with defining simple spring boot configuration:

package com.acme.springboot.configure

import jakarta.annotation.PostConstruct
import org.springframework.context.annotation.Bean

@AutoConfiguration
class CustomAutoConfiguration(
    private val props: CustomProperties
) {

    private val log = org.slf4j.LoggerFactory.getLogger(CustomAutoConfiguration::class.java)
    
    @Bean
    fun myBean(): String {
        return "I am a bean!"
    }

    @PostConstruct
    fun postConstruct() {
        log.info("CustomAutoConfiguration initialized.", foo)
    }
}

Let’s create file META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports in src/main/resources:

com.acme.springboot.configure.CustomAutoConfiguration

Now our configuration will be loaded during application startup.

Adding Custom Properties #

If your starter brings some functionality, it often requires some additional configuration. Spring boot starters allow to package this custom configuration in Jar file.

Let’s define custom property file and save it to src/main/resources:

acme.foo=bar
acme.bar=42
acme.baz=true

Now, let’s make this property file available in the Spring Boot Configuration:

package com.acme.springboot.configure

import jakarta.annotation.PostConstruct
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.PropertySource

@AutoConfiguration
@PropertySource("classpath:/acme.properties")
class CustomAutoConfiguration(
    private val props: CustomProperties
) {

    private val log = org.slf4j.LoggerFactory.getLogger(CustomAutoConfiguration::class.java)

    @Value("\${acme.foo}")
    private lateinit var foo: String

    @Bean
    fun myBean(): String {
        return "I am a bean!"
    }

    @PostConstruct
    fun postConstruct() {
        log.info("CustomAutoConfiguration initialized. foo={}", foo)
    }
}

We can also expose configuration as Bean:

package com.acme.springboot.configure

import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "acme")
data class CustomProperties(
    var foo: String,
    var bar: Int = 0,
    var baz: Boolean = false,
)

and load this bean in spring

package com.acme.springboot.configure

import jakarta.annotation.PostConstruct
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.PropertySource

@AutoConfiguration
@PropertySource("classpath:/acme.properties")
@EnableConfigurationProperties(CustomProperties::class)
class CustomAutoConfiguration(
    private val props: CustomProperties
) {

    private val log = org.slf4j.LoggerFactory.getLogger(CustomAutoConfiguration::class.java)

    @Value("\${acme.foo}")
    private lateinit var foo: String

    @Bean
    fun myBean(): String {
        return "I am a bean!"
    }

    @PostConstruct
    fun postConstruct() {
        log.info("CustomAutoConfiguration initialized. foo={}", foo)
        log.info("CustomProperties: {}", props)
    }
}

Adding Custom Resources #

To implement custom logging configuration we can leverage standard spring boot mechanism and provide our logback-spring.xml file.

Let’s put our logback-spring.xml to src/main/respurces:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <springProperty scope="context" name="service" source="spring.application.name"/>

    <springProfile name="!text-logging">
        <!-- https://github.com/logfellow/logstash-logback-encoder -->
        <appender name="console-sync" class="ch.qos.logback.core.ConsoleAppender">
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>${CONSOLE_LOG_THRESHOLD}</level>
            </filter>
            <encoder class="net.logstash.logback.encoder.LogstashEncoder">
                <includeMdc>true</includeMdc>
                <timeZone>UTC</timeZone>
                <fieldNames>
                    <levelValue>[ignore]</levelValue>
                    <logger>logger</logger>
                    <stackTrace>exception</stackTrace>
                    <thread>thread</thread>
                    <timestamp>timestamp</timestamp>
                    <version>version</version>
                </fieldNames>
                <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter">
                    <maxDepthPerThrowable>30</maxDepthPerThrowable>
                    <maxLength>2048</maxLength>
                    <shortenedClassNameLength>30</shortenedClassNameLength>
                    <exclude>sun\.reflect\..*\.invoke.*</exclude>
                    <exclude>net\.sf\.cglib\.proxy\.MethodProxy\.invoke</exclude>
                    <rootCauseFirst>true</rootCauseFirst>
                    <inlineHash>true</inlineHash>
                    <lineSeparator>\\n</lineSeparator>
                </throwableConverter>
            </encoder>
        </appender>
        <appender name="CONSOLE" class="net.logstash.logback.appender.LoggingEventAsyncDisruptorAppender">
            <appender-ref ref="console-sync"/>
        </appender>
    </springProfile>
    <springProfile name="text-logging">
        <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
    </springProfile>
    <!-- Appender to log to console -->

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

We can leverage logging.config property in Spring Boot to specify the location of a custom logging configuration file. This property allows to fine-tune the logging behavior of their application beyond the defaults provided by Spring Boot.

When the logging.config property is set, it tells Spring Boot to use the specified configuration file for logging instead of the auto-configured defaults. This is particularly useful for applications that need specific logging configurations that are not covered by Spring Boot’s conventional setup.

We can include src/main/resources/config/application.yaml https://github.com/kpavlov/acme-spring-boot-starters/blob/main/starters/acme-spring-boot-starter-base/src/main/resources/config/application.yaml to spring boot starter to override logging.config and other common configuration options:

spring:
  main:
    banner-mode: off

logging:
  config: classpath:acme-logback-spring.xml

We are including application.yaml under config/ folder in the resources to avoid conflict with application.yaml, located in the resources root of the microservice. Read more about externalizing configurations here.

Please note, that we also need to include Logback logstash encoder in pom.xml:

 <dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>${logstash-logback-encoder.version}</version>
    <scope>runtime</scope>
</dependency>

Let’s Test! #

Let’s write a test for our sample application verifying that application properties, defined in the starter, are available:

package com.acme.springboot.sample

import com.acme.springboot.configure.CustomProperties
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest

@SpringBootTest
class CustomPropertiesTest {

    @Autowired(required = false)
    private var customProps: CustomProperties? = null

    @Autowired(required = false)
    private var myBean: String? = null

    @Test
    fun `Should have CustomProperties`() {
        assertThat(customProps).isNotNull
        customProps?.let { props ->
            assertThat(props.foo).isEqualTo("bar")
            assertThat(props.bar).isEqualTo(42)
            assertThat(props.baz).isTrue
        }
    }

    @Test
    fun `Should have myBean`() {
        assertThat(myBean).isEqualTo("I am a bean!")
    }
}

We can also include application-text.yaml file:

spring:
  profiles:
    active: default # Set to "text-logging" to use text logging instead of JSON

With default profile application will write logs to console as json. For better development experience it is more convenient to have logs in text format, so one may change it here or with @ActiveProfiles("text-logging") annotation in test class.

Conclusion #

In conclusion, Spring Boot Starters offer a powerful and streamlined way to manage dependencies and configurations across Spring applications. By leveraging these starters, developers can significantly reduce boilerplate code, ensure consistency in dependency management, and focus on building the unique aspects of their applications. Embracing Spring Boot Starters is a step towards more efficient and effective Spring application development, allowing developers to enjoy a convention-over-configuration approach that accelerates the path from concept to production.

Source code with the examples is available on GitHub: kpavlov/acme-spring-boot-starters

Official Spring Boot Starters: https://spring.io/projects

YouTube video: “How to create your own custom Spring Boot Starter”