Build a Private Spring Boot Starter from Scratch

This tutorial walks through the complete process of creating a custom Spring Boot starter—covering its core concepts, Maven setup, configuration properties, auto‑configuration class, conditional bean loading, packaging, testing, and best‑practice tips—so developers can encapsulate reusable components and plug them into other projects with zero configuration effort.

Java Tech Workshop
Java Tech Workshop
Java Tech Workshop
Build a Private Spring Boot Starter from Scratch

What is a Spring Boot Starter?

A starter is a Maven dependency that bundles three parts: public business code or utilities, an auto‑configuration class (using @EnableAutoConfiguration), and a META-INF/spring.factories file that registers the configuration class. Its purpose is to encapsulate reusable functionality and provide "out‑of‑the‑box" behavior.

Core Principle of a Custom Starter

Package public components as beans via a @Configuration class.

Use @Conditional annotations (e.g., @ConditionalOnProperty) to load beans only when required.

Register the auto‑configuration class in META-INF/spring.factories.

When another project adds the starter as a dependency, Spring Boot automatically loads the configuration and injects the beans.

Naming Convention

Official starters follow spring-boot-starter-xxx. Custom starters should use xxx-spring-boot-starter (e.g., custom-sms-spring-boot-starter) to avoid name clashes.

Step‑by‑Step Development

1. Create Maven Project

Initialize a Maven module with groupId (e.g., com.example) and

artifactId
custom-sms-spring-boot-starter

. Set packaging to jar. Add the following dependencies (scope provided to avoid version conflicts):

<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>custom-sms-spring-boot-starter</artifactId>
  <version>1.0.0</version>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <version>2.7.10</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <version>2.7.10</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.24</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
</project>

2. Define Configuration Properties

Create SmsProperties bound to the sms prefix:

package com.example.sms.config;

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

@Data
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
    /** Enable switch, default false */
    private boolean enable = false;
    /** Platform type: aliyun or tencent */
    private String platform;
    private String appId;
    private String appSecret;
    private String signName;
}

3. Implement SMS Service Interface

Define a common interface:

package com.example.sms.service;

/** Unified SMS sending interface */
public interface SmsService {
    /** Send SMS */
    boolean sendSms(String phone, String content);
}

Provide two implementations (Aliyun and Tencent) that receive the properties via setter injection and simulate sending:

package com.example.sms.service.impl;

import com.example.sms.service.SmsService;
import lombok.Data;

@Data
public class AliyunSmsServiceImpl implements SmsService {
    private String appId;
    private String appSecret;
    private String signName;
    @Override
    public boolean sendSms(String phone, String content) {
        System.out.println("Aliyun SMS -> appId: " + appId + ", appSecret: " + appSecret);
        System.out.println("To " + phone + ", content: " + content + ", sign: " + signName);
        return true; // simulate success
    }
}

package com.example.sms.service.impl;

import com.example.sms.service.SmsService;
import lombok.Data;

@Data
public class TencentSmsServiceImpl implements SmsService {
    private String appId;
    private String appSecret;
    private String signName;
    @Override
    public boolean sendSms(String phone, String content) {
        System.out.println("Tencent SMS -> appId: " + appId + ", appSecret: " + appSecret);
        System.out.println("To " + phone + ", content: " + content + ", sign: " + signName);
        return true;
    }
}

4. Write Auto‑Configuration Class

The heart of the starter registers beans conditionally:

package com.example.sms.config;

import com.example.sms.service.SmsService;
import com.example.sms.service.impl.AliyunSmsServiceImpl;
import com.example.sms.service.impl.TencentSmsServiceImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(SmsProperties.class)
@ConditionalOnClass(SmsService.class)
@ConditionalOnProperty(prefix = "sms", name = "enable", havingValue = "true", matchIfMissing = false)
public class SmsAutoConfiguration {
    private final SmsProperties smsProperties;
    public SmsAutoConfiguration(SmsProperties smsProperties) {
        this.smsProperties = smsProperties;
    }
    @Bean
    @ConditionalOnProperty(prefix = "sms", name = "platform", havingValue = "aliyun")
    @ConditionalOnMissingBean
    public SmsService aliyunSmsService() {
        AliyunSmsServiceImpl aliyunSms = new AliyunSmsServiceImpl();
        aliyunSms.setAppId(smsProperties.getAppId());
        aliyunSms.setAppSecret(smsProperties.getAppSecret());
        aliyunSms.setSignName(smsProperties.getSignName());
        return aliyunSms;
    }
    @Bean
    @ConditionalOnProperty(prefix = "sms", name = "platform", havingValue = "tencent")
    @ConditionalOnMissingBean
    public SmsService tencentSmsService() {
        TencentSmsServiceImpl tencentSms = new TencentSmsServiceImpl();
        tencentSms.setAppId(smsProperties.getAppId());
        tencentSms.setAppSecret(smsProperties.getAppSecret());
        tencentSms.setSignName(smsProperties.getSignName());
        return tencentSms;
    }
}

5. Register in META-INF/spring.factories

Create src/main/resources/META-INF/spring.factories with the following line:

# Spring Boot auto‑configuration list
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.sms.config.SmsAutoConfiguration

6. Package the Starter

Run Maven clean and package. The resulting JAR (e.g., custom-sms-spring-boot-starter-1.0.0.jar) can be installed to a local repository or uploaded to a private Nexus repository for sharing.

Testing the Starter

Create a new Spring Boot test project and add the starter and spring-boot-starter-web dependencies.

Configure application.yml:

server:
  port: 8080
sms:
  enable: true
  platform: aliyun   # change to tencent to switch implementation
  app-id: 123456
  app-secret: abcdef123456
  sign-name: MyTestSignature

Write a controller that injects SmsService and exposes /sendSms:

package com.example.test.controller;

import com.example.sms.service.SmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SmsTestController {
    @Autowired
    private SmsService smsService;
    @GetMapping("/sendSms")
    public String sendSms(@RequestParam String phone, @RequestParam String content) {
        boolean result = smsService.sendSms(phone, content);
        return result ? "短信发送成功" : "短信发送失败";
    }
}

Run the test application. Spring Boot loads SmsAutoConfiguration, creates the appropriate bean (Aliyun or Tencent), and the endpoint prints simulated send logs. Switching sms.platform to tencent demonstrates conditional bean selection.

Best Practices & Pitfalls

Dependency Management: Only include core dependencies (e.g., spring-boot-autoconfigure) to avoid pulling in unwanted libraries.

Default Values: Provide sensible defaults (e.g., enable = false) and validate required fields to prevent NPEs.

Conditional Annotations: Use @ConditionalOnProperty, @ConditionalOnClass, and @ConditionalOnMissingBean to achieve true on‑demand loading.

Naming: Follow the xxx-spring-boot-starter pattern and keep configuration prefixes unique.

Documentation: Add a README that explains purpose, required properties, usage steps, and compatible Spring Boot versions.

Interview‑Level Checklist

Core components of a custom starter: configuration properties class, auto‑configuration class, and META-INF/spring.factories entry.

Spring Boot discovers the auto‑configuration class via SpringFactoriesLoader reading the factories file.

Conditional loading is achieved with the @Conditional family (property switch, class presence, missing bean).

Differences from official starters: naming convention, business‑specific functionality, and maintenance responsibility.

Conclusion

Creating a private Spring Boot starter is straightforward once the auto‑configuration and conditional‑annotation concepts are understood. By following the six‑step workflow—Maven project → properties → business logic → auto‑configuration → factories registration → packaging—developers can package reusable modules, reduce duplicate code, and showcase solid Spring Boot expertise.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

mavenspring-bootAuto-configurationCustom StarterConditionalOnProperty
Java Tech Workshop
Written by

Java Tech Workshop

Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.