Spring Boot Starters

What a starter actually is, how auto-configuration is triggered, and when to exclude or write your own

← Back to Index

What a Starter Actually Is — Two Things, Not One

The name "starter" suggests a single mechanism. It's actually two separate things working together, and conflating them is the source of most "why did adding this dependency change my app's behaviour" confusion:

PartWhat it isWhere the code lives
The starter POM An empty JAR whose only content is a pom.xml declaring a curated set of transitive dependencies at compatible versions No Java code at all — pure Maven dependency management
Auto-configuration @AutoConfiguration classes in the pulled-in libraries that Spring Boot registers and conditionally activates Inside the actual library JARs (spring-webmvc, jackson-databind, etc.), not in the starter itself

Adding spring-boot-starter-web to your POM does two things at once: it pulls in spring-webmvc, spring-boot-starter-tomcat, jackson-databind, and a handful of others (the starter's job), and those JARs each ship @AutoConfiguration classes that Spring Boot discovers and conditionally activates (auto-configuration's job). The starter is the trigger; the auto-configuration is the actual behaviour change.

How auto-configuration is discovered: AutoConfiguration.imports

Since Spring Boot 2.7, each library that provides auto-configuration ships a file at META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports listing its @AutoConfiguration class names. Spring Boot reads this file on startup and registers every class in it as a candidate. Before 2.7, the same information lived in META-INF/spring.factories under the EnableAutoConfiguration key — that file still works for backwards compatibility, but new libraries use AutoConfiguration.imports.

Common Starters and What They Actually Pull In

StarterKey transitive dependenciesAuto-configuration effect
spring-boot-starter-web spring-webmvc, embedded Tomcat, jackson-databind Configures DispatcherServlet, starts Tomcat, registers Jackson HttpMessageConverter
spring-boot-starter-data-jpa spring-data-jpa, Hibernate, spring-jdbc, HikariCP Configures EntityManagerFactory, connection pool, transaction manager, repository scanning
spring-boot-starter-security spring-security-web, spring-security-config Registers DelegatingFilterProxy, secures all endpoints by default — see Spring Security
spring-boot-starter-validation jakarta.validation-api, hibernate-validator Registers MethodValidationPostProcessor; enables @Valid on controller parameters
spring-boot-starter-test JUnit 5, Mockito, AssertJ, Spring Test, Hamcrest No runtime auto-configuration — test-scope only
spring-boot-starter-actuator spring-boot-actuator, Micrometer Exposes /actuator/health, /actuator/metrics and other management endpoints
spring-boot-starter-cache spring-context-support Enables @EnableCaching infrastructure; a cache provider (Caffeine, Redis) must still be added separately
spring-boot-starter-webflux spring-webflux, Reactor Netty Configures reactive DispatcherHandler on Netty — mutually exclusive with starter-web on the same classpath
<!-- pom.xml — no <version> needed: spring-boot-starter-parent manages all versions -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Excluding Auto-Configuration

Auto-configuration is conditional — every @AutoConfiguration class is gated by one or more @ConditionalOn* annotations (@ConditionalOnClass, @ConditionalOnMissingBean, etc.) and only activates when those conditions are met. Usually this means it stays out of your way — the moment you define your own DataSource bean, DataSourceAutoConfiguration backs off. Sometimes, though, you need to exclude it explicitly:

// Exclude at the application level — affects all contexts
@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,       // module that has no database
    SecurityAutoConfiguration.class           // internal tool with no login required
})
public class MyApp { ... }

// Or via properties — useful when you can't modify the main class
# application.properties
spring.autoconfigure.exclude=\
  org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
  org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
How to find what auto-configuration is active in your app

Add spring-boot-starter-actuator and hit /actuator/conditions — it lists every @AutoConfiguration candidate, whether it matched, and which @ConditionalOn* condition decided the outcome. Alternatively, start the app with --debug (or logging.level.org.springframework.boot.autoconfigure=DEBUG) to get the same report in the console as the "CONDITIONS EVALUATION REPORT". This is the correct diagnostic tool, not trial-and-error with exclusions.

Writing a Custom Starter

A custom starter is the right pattern when a cross-cutting concern — audit logging, a company-wide HTTP client configuration, a shared security policy — needs to be reused across multiple Spring Boot applications without each team re-implementing it. The structure follows the same two-part pattern as official starters:

/*
 * Recommended layout:
 *
 *   my-feature-spring-boot-starter/     ← empty POM, declares the autoconfigure module
 *   my-feature-spring-boot-autoconfigure/ ← the actual @AutoConfiguration class + conditions
 *
 * Naming convention: {feature}-spring-boot-starter
 * Official starters use spring-boot-starter-{feature} — the reversed form is reserved for
 * Spring's own starters; third-party and internal starters use your-name first.
 */

// 1. The auto-configuration class
@AutoConfiguration
@ConditionalOnClass(AuditService.class)          // only if the feature is on the classpath
@ConditionalOnMissingBean(AuditService.class)    // back off if the app defined its own
@EnableConfigurationProperties(AuditProperties.class)
public class AuditAutoConfiguration {

    @Bean
    public AuditService auditService(AuditProperties props) {
        return new DefaultAuditService(props);
    }
}
# 2. Register it for discovery
# src/main/resources/META-INF/spring/
#   org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.example.audit.AuditAutoConfiguration
<!-- 3. The starter POM — no Java code, just dependency declarations -->
<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>my-feature-spring-boot-autoconfigure</artifactId>
    </dependency>
    <!-- any other transitive dependencies the feature needs -->
</dependencies>
@ConditionalOnMissingBean is what makes auto-configuration non-invasive

Without @ConditionalOnMissingBean, your starter's bean would conflict with any application that defines its own. With it, the auto-configured default steps aside the moment the consuming application declares its own bean of that type — the consuming application always wins. This is the contract that makes every official Spring Boot starter safe to add without fear of overriding something the developer intentionally configured.

Interview Questions

🎓 Junior level

Q: What is a Spring Boot starter?
A starter is a POM-only JAR that declares a curated set of transitive dependencies at compatible versions. It contains no Java code — its sole purpose is to pull in the right libraries so you don't have to manually manage each one and their version compatibility.

Q: Does adding a starter automatically configure the feature?
Not directly. The starter pulls in library JARs; those JARs ship @AutoConfiguration classes that Spring Boot discovers via AutoConfiguration.imports. The auto-configuration activates conditionally — usually only if certain classes are on the classpath and no conflicting bean is already defined.

🔥 Senior level

Q: How does Spring Boot discover auto-configuration classes, and how did this change in Boot 2.7?
Before 2.7, auto-configuration class names were listed in META-INF/spring.factories under the EnableAutoConfiguration key. From 2.7, the preferred mechanism is a dedicated file at META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, one class name per line. The old spring.factories key still works for backwards compatibility but new starters — including all official Spring ones — use AutoConfiguration.imports.

Q: What is the role of @ConditionalOnMissingBean in a starter's auto-configuration, and what breaks without it?
It causes the auto-configured bean to register only if no bean of that type already exists in the context. Without it, adding the starter to an application that intentionally defines its own implementation of the same type would create a duplicate bean conflict — NoUniqueBeanDefinitionException at startup. With it, the application's explicit bean takes precedence and the starter's default silently backs off. This is the contract that makes auto-configuration non-invasive by design.