Building a custom Spring Boot Starter: From theory to practice

Building a custom Spring Boot Starter: From theory to practice

Introduction

Spring Boot starters are key feature of the Spring Boot ecosystem. They simplify dependency management and auto — configuration. A starter mix of dependencies and auto — configuration.It provides specific features with little setup needed. Spring Boot offers many official starters. However, you may need to create a custom starter for reusable functionality across different projects.

In this guide, we’ll look at the theory and practice fo building a custom Spring Boot starter. We’ll learn how to start working under the hood and create a real — world example that you can use as template for you own custom starter.

Understanding Spring Boot starter

What is a Spring Boot starter?

A Spring Boot starter is a special type of module that serves two primary purposes:

  1. Provide a curated set of dependencies that integrate smoothly with one another.
  2. Offers auto — configuration capabilities to enable specific functionaility with minimal configuration.

Key components of a custom starter

A typical custom starter consists of the following components:

  1. Autoconfigure module: contains the auto-configuration code and condition — based configuration.
  2. Starter module: Alightweight module. It includes the autoconfigure module and the necessary dependencies. 
  3. Properties class: Defines configurable properties for your starter.
  4. Configuration class: Contains the actual beams and configuration logic.

Naming conventions

Spring Boot has specific naming conventions for custom starters:

  • Autoconfigure module: {name}-spring-boot-autoconfigure. 
  • Starter module: {name}-spring-boot-starter 
  • Don’t start you module name with spring boot unless you’re contributing to the Spring Boot project.

Practical implementation

Let’s build a custom starter that offers a basic messaging service. You can make suimple adjustments to its settings. This example will show all the essential components of a custom starter.

Project structure

message-service-spring-boot-starter/
├── message-service-spring-boot-autoconfigure/
│ └── src/main/java/
│ └── com/example/message/
│ ├── autoconfigure/
│ │ ├── MessageServiceAutoConfiguration.java
│ │ └── MessageServiceProperties.java
│ └── service/
│ ├── MessageService.java
│ └── MessageServiceImpl.java
└── message-service-spring-boot-starter/
└── pom.xml

Implementation

Step 1. Create the autoconfigure module

First, let’s create the pom.xml for the autoconfigure module.

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>message-service-spring-boot-autoconfigure</artifactId>
    <version>1.0.0</version>

    <properties>
        <spring-boot.version>3.2.0</spring-boot.version>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>${spring-boot.version}</version>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

Step 2. Create properties class

package com.example.message.autoconfigure;

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

@ConfigurationProperties(prefix = "message.service")
public class MessageServiceProperties {
    private String prefix = "Message: ";
    private String suffix = "";
    private boolean uppercase = false;

    // Getters and setters
    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }

    public boolean isUppercase() {
        return uppercase;
    }

    public void setUppercase(boolean uppercase) {
        this.uppercase = uppercase;
    }
}

Step 3. Create the service interface and implementation

package com.example.message.service;

public interface MessageService {
    String getMessage(String message);
}

package com.example.message.service;

import com.example.message.autoconfigure.MessageServiceProperties;

public class MessageServiceImpl implements MessageService {
    private final MessageServiceProperties properties;

    public MessageServiceImpl(MessageServiceProperties properties) {
        this.properties = properties;
    }

    @Override
    public String getMessage(String message) {
        String processedMessage = properties.getPrefix() + message + properties.getSuffix();
        return properties.isUppercase() ? processedMessage.toUpperCase() : processedMessage;
    }
}

Step 4: Create the auto — configuration class

package com.example.message.autoconfigure;

import com.example.message.service.MessageService;
import com.example.message.service.MessageServiceImpl;
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
@EnableConfigurationProperties(MessageServiceProperties.class)
public class MessageServiceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MessageService messageService(MessageServiceProperties properties) {
        return new MessageServiceImpl(properties);
    }
}

Step 5. Create an auto — configuration registration file

As of Spring Boot 3, the registration of auto — configuration has moved from spring.factories to a new location. Create a file src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:

com.example.message.autoconfigure.MessageServiceAutoConfiguration

This new mechanism boost modularity and speeds up startup. Now, Spring Boot doesn’t have to scanl all JARs for the spring.factories file

Important notes

  • One auto — configuration class per line.
  • No line continuation characters are neeed (unlike in spring.factories)
  • The file must be in UTF-8 encoding.
  • Comments can be added using # at the start of the line.
  • You can control the configuration order using @AutoConfiguration(before = {...}, after = {...}) on your classes.
@AutoConfiguration(
    before = DataSourceAutoConfiguration.class,
    after = ValidationAutoConfiguration.class
)
@EnableConfigurationProperties(MessageServiceProperties.class)
public class MessageServiceAutoConfiguration {
    // configuration code

Step 6. Create the starter module

Create a pom.xml for the starter module.

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>message-service-spring-boot-starter</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>message-service-spring-boot-autoconfigure</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>
</project>

Using the custom starter

To use a custom start in another project, add the following dependency.

<dependency>
    <groupId>com.example</groupId>
    <artifactId>message-service-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

Conffigure the service using prioperties in application properties or application.yml

message:
  service:
    prefix: "Hello, "
    suffix: "!"
    uppercase: true

Use the service in your application

@RestController
public class MessageController {
    private final MessageService messageService;

    public MessageController(MessageService messageService) {
        this.messageService = messageService;
    }

    @GetMapping("/message/{name}")
    public String getMessage(@PathVariable String name) {
        return messageService.getMessage(name);
    }
}

Best practices

  1. Modularity: Keep the autoconfigure module separate from the starter module.
  2. Conditional configuration: Use Spring Boot’s @ConditionalOn annotation to make the configuration flexible.
  3. Documentation: Include comprehensive documentation about configuration properties.
  4. Testing: Write a thorough test for you starters functionality.
  5. Naming: Follow Spring Boot’s naming conventions for custom starters.
  6. Dependencies: Keep the dependency set minimal and focused.

Conclusion

Creating a custom Spring Boot starter is a powerful way to package and share functionality across many projects. Follow the principles in this guide to create well — structured, maintainable, and resuable starters. They will fit seamlessly into ther Spring Boot ecosystem.

The example shows the key parts of a custom starter. It also follows Spring Boot’s best practices and conventions. You can apply this knowledge to create more complex starter for you specific use cases.

References

1. Spring Boot Reference Documentation
2. Creating Your Own Auto-configuration
3. Spring Boot Maven Plugin Reference Guide
4. Spring Boot Configuration Metadata
5. Spring Boot Testing

Subscribe to Egor Voronianskii | Java Development and whatsoever

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe