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.
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.SmsAutoConfiguration6. 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: MyTestSignatureWrite 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
