package com.example.starter.config;
import com.example.starter.properties.CustomProperties;
import com.example.starter.service.CustomService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
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;
@AutoConfiguration
@ConditionalOnClass(CustomService.class)
@EnableConfigurationProperties(CustomProperties.class)
@ConditionalOnProperty(
prefix = "custom.service",
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
public class CustomAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CustomService customService(CustomProperties properties) {
return new CustomService(
properties.getApiKey(),
properties.getTimeout(),
properties.getRetryAttempts()
);
}
}
package com.example.starter.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
@ConfigurationProperties(prefix = "custom.service")
public class CustomProperties {
/**
* Enable or disable the custom service
*/
private boolean enabled = true;
/**
* API key for authentication
*/
@NotBlank(message = "API key is required")
private String apiKey;
/**
* Request timeout in milliseconds
*/
@Min(value = 1000, message = "Timeout must be at least 1000ms")
private int timeout = 5000;
/**
* Number of retry attempts
*/
@Min(value = 0, message = "Retry attempts must be non-negative")
private int retryAttempts = 3;
// Getters and Setters
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getRetryAttempts() {
return retryAttempts;
}
public void setRetryAttempts(int retryAttempts) {
this.retryAttempts = retryAttempts;
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.starter.config.CustomAutoConfiguration
<project>
<groupId>com.example</groupId>
<artifactId>custom-spring-boot-starter</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
Spring Boot starters provide autoconfiguration for libraries. I create custom starters to standardize configurations across microservices. Starters contain @Configuration classes with @ConditionalOnClass, @ConditionalOnProperty annotations. spring.factories registers auto-configuration classes. Starters follow naming convention: xxx-spring-boot-starter. Configuration properties use @ConfigurationProperties with metadata for IDE support. Starters encapsulate library setup, reducing boilerplate in applications. Order matters—@AutoConfigureAfter, @AutoConfigureBefore control sequence. Custom starters promote consistency, enable centralized updates, and simplify dependency management. They're essential for maintaining standards across large organizations with many Spring Boot applications.