Starter Dependencies
Why Starter Dependencies Matter
Spring Boot starters bundle compatible versions of related dependencies into single coordinates, eliminating version conflicts and reducing pom.xml complexity from 50+ dependencies to 5-10 starters. In production systems using Spring Data JPA, Spring Security, Redis, Kafka, and monitoring, manual dependency management causes runtime ClassNotFoundExceptions and version incompatibilities—starters guarantee tested combinations that work together.
Core Benefits:
- Simplified dependency management: One starter replaces 10+ manual dependencies
- Version compatibility: Tested dependency combinations, no ClassNotFoundException
- Transitive dependencies: Spring Boot manages sub-dependencies automatically
- Auto-configuration: Starters trigger auto-configuration for included libraries
- Production-ready: Starters include production-grade defaults (connection pooling, JSON parsing)
Problem: Manual dependency management requires knowing exact coordinates, versions, and transitive dependencies for every library.
Solution: Spring Boot starters provide curated dependency bundles with compatible versions and auto-configuration.
Manual Maven Dependency Management
Traditional Spring projects require explicit dependency declarations for every library:
<!-- pom.xml: Manual dependency management (before Spring Boot) -->
<project>
<dependencies>
<!-- Spring Framework Core (5 dependencies) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.3</version>
<!-- => Must specify exact version for every artifact -->
<!-- => Version mismatch between spring-context and spring-jdbc
causes runtime errors -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.1.3</version>
<!-- => Must keep version synchronized with spring-context -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>6.1.3</version>
<!-- => Transaction management: separate artifact -->
</dependency>
<!-- Spring Data JPA (8 dependencies) -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>3.2.2</version>
<!-- => Spring Data version must match Spring Framework version -->
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.4.2.Final</version>
<!-- => Hibernate version must be compatible with Spring Data JPA -->
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.1.0</version>
<!-- => JPA API: version must match Hibernate -->
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
<!-- => Connection pool: must find compatible version manually -->
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.1</version>
<!-- => JDBC driver: version compatibility with JDBC API -->
</dependency>
<!-- Spring Web (12 dependencies) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.1.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.1.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
<!-- => JSON parsing: must be compatible with Spring -->
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>10.1.18</version>
<!-- => Embedded server: manual version management -->
</dependency>
<!-- ... 8 more Tomcat dependencies ... -->
<!-- Testing (6 dependencies) -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.1.3</version>
<scope>test</scope>
</dependency>
<!-- ... 4 more test dependencies ... -->
</dependencies>
</project>
<!-- Result: 50+ dependencies for basic Spring JPA web app -->Limitations:
- Verbose: 50+ dependency declarations for basic app
- Error-prone: Version mismatches cause ClassNotFoundException at runtime
- Maintenance burden: Upgrading Spring requires updating 15+ version tags
- No transitive management: Must manually add transitive dependencies
- No auto-configuration: Dependencies don’t trigger Spring Boot auto-config
Spring Boot Starter Dependencies
Spring Boot provides starter POMs bundling compatible dependencies:
<!-- pom.xml: Spring Boot starters (simplified) -->
<project>
<!-- Parent POM: provides dependency management -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<!-- => spring-boot-starter-parent:
- Defines versions for 500+ managed dependencies
- Provides Maven plugin configuration
- Sets Java version, encoding, compiler settings -->
</parent>
<dependencies>
<!-- Spring Boot Web Starter: REST APIs + embedded Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- => No <version> tag: inherited from parent POM -->
<!-- => Includes:
- spring-boot-starter (core Spring Boot)
- spring-boot-starter-tomcat (embedded Tomcat)
- spring-webmvc (Spring MVC)
- jackson-databind (JSON parsing)
- spring-boot-starter-json (JSON support)
- All transitive dependencies (30+ artifacts) -->
</dependency>
<!-- Spring Boot Data JPA Starter: JPA + Hibernate + connection pool -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<!-- => Includes:
- spring-data-jpa (Spring Data repositories)
- hibernate-core (JPA implementation)
- jakarta.persistence-api (JPA API)
- HikariCP (connection pool)
- spring-jdbc (JDBC support)
- spring-tx (transaction management)
- All compatible versions tested together -->
</dependency>
<!-- PostgreSQL JDBC Driver: only driver needed explicitly -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
<!-- => Version managed by spring-boot-dependencies BOM -->
</dependency>
<!-- Spring Boot Test Starter: JUnit + Spring Test + MockMvc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- => Includes:
- junit-jupiter (JUnit 5)
- spring-test (Spring testing support)
- spring-boot-test (Spring Boot test utilities)
- mockito-core (mocking framework)
- assertj-core (fluent assertions)
- hamcrest (matchers)
- All compatible versions -->
</dependency>
</dependencies>
</project>
<!-- Result: 4 dependencies replace 50+ manual declarations -->How starters work:
graph TD
A["spring-boot-starter-web"] -->|"Depends on"| B["spring-boot-starter"]
A -->|"Depends on"| C["spring-boot-starter-json"]
A -->|"Depends on"| D["spring-boot-starter-tomcat"]
B -->|"Depends on"| E["spring-core, spring-context"]
C -->|"Depends on"| F["jackson-databind"]
D -->|"Depends on"| G["tomcat-embed-core"]
H["spring-boot-dependencies BOM"] -->|"Manages versions"| A
H -->|"Manages versions"| B
H -->|"Manages versions"| E
H -->|"Manages versions"| F
H -->|"Manages versions"| G
I["Application"] -->|"Declares"| A
A -->|"Triggers"| J["WebMvcAutoConfiguration"]
J -->|"Configures"| K["DispatcherServlet, Embedded Tomcat"]
style A fill:#0173B2,color:#fff
style H fill:#DE8F05,color:#fff
style J fill:#029E73,color:#fff
Trade-offs:
- Added complexity: Transitive dependencies hidden, harder to troubleshoot
- Less control: Starter versions determined by Spring Boot BOM
- Larger JARs: Starters include dependencies you might not need
- Justification: Worth it for projects using multiple Spring modules (>3 dependencies)
Common Spring Boot Starters
<!-- Production-ready starters -->
<!-- spring-boot-starter-web: REST APIs, Spring MVC, embedded Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- => Includes: Spring MVC, Jackson, Tomcat, validation -->
<!-- => Use case: REST APIs, web applications -->
</dependency>
<!-- spring-boot-starter-data-jpa: JPA repositories, Hibernate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<!-- => Includes: Spring Data JPA, Hibernate, HikariCP, JDBC -->
<!-- => Use case: SQL databases (PostgreSQL, MySQL, Oracle) -->
</dependency>
<!-- spring-boot-starter-security: Authentication, authorization -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<!-- => Includes: Spring Security, authentication filters, CSRF protection -->
<!-- => Use case: Authentication (JWT, OAuth2), authorization -->
</dependency>
<!-- spring-boot-starter-data-redis: Redis caching, session storage -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!-- => Includes: Lettuce Redis client, Spring Data Redis -->
<!-- => Use case: Caching, session storage, distributed locks -->
</dependency>
<!-- spring-boot-starter-validation: Bean Validation (JSR 380) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<!-- => Includes: Hibernate Validator, Jakarta Bean Validation API -->
<!-- => Use case: Input validation (@NotNull, @Email, @Size) -->
</dependency>
<!-- spring-boot-starter-actuator: Production monitoring, health checks -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<!-- => Includes: Actuator endpoints, metrics (Micrometer), health checks -->
<!-- => Use case: /actuator/health, /actuator/metrics, production monitoring -->
</dependency>
<!-- spring-boot-starter-webflux: Reactive web, non-blocking I/O -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<!-- => Includes: Spring WebFlux, Project Reactor, Netty -->
<!-- => Use case: Reactive streams, high concurrency (>10K connections) -->
</dependency>
<!-- spring-boot-starter-kafka: Kafka producer/consumer -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-kafka</artifactId>
<!-- => Includes: Kafka client, Spring Kafka templates -->
<!-- => Use case: Event streaming, message queues -->
</dependency>
<!-- spring-boot-starter-test: Testing (JUnit, Mockito, Spring Test) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- => Includes: JUnit 5, Spring Test, Mockito, AssertJ, MockMvc -->
<!-- => Use case: Unit tests, integration tests, MockMvc -->
</dependency>Creating Custom Starters
Production libraries can provide custom starters for their services:
<!-- Custom starter structure: zakat-spring-boot-starter -->
<!-- File: pom.xml -->
<project>
<groupId>com.example</groupId>
<artifactId>zakat-spring-boot-starter</artifactId>
<version>1.0.0</version>
<name>Zakat Spring Boot Starter</name>
<dependencies>
<!-- Auto-configuration module: provides @Configuration classes -->
<dependency>
<groupId>com.example</groupId>
<artifactId>zakat-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
<!-- => Contains ZakatAutoConfiguration class -->
</dependency>
<!-- Core library: business logic -->
<dependency>
<groupId>com.example</groupId>
<artifactId>zakat-calculator</artifactId>
<version>${project.version}</version>
<!-- => ZakatCalculator, ZakatService implementation -->
</dependency>
<!-- Spring Boot core: required for auto-configuration -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<!-- => Version managed by spring-boot-dependencies BOM -->
</dependency>
<!-- Optional dependencies: starter includes but doesn't force -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<optional>true</optional>
<!-- => Optional: only needed if ZakatRepository used -->
</dependency>
</dependencies>
</project>Auto-configuration module:
// => zakat-spring-boot-autoconfigure/src/main/java/.../ZakatAutoConfiguration.java
@Configuration
@ConditionalOnClass(ZakatCalculator.class)
@EnableConfigurationProperties(ZakatProperties.class)
public class ZakatAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ZakatCalculator zakatCalculator(ZakatProperties properties) {
// => Auto-configured ZakatCalculator bean
return new ZakatCalculator(
properties.getNisabGrams(),
properties.getRate()
);
}
@Bean
@ConditionalOnClass(name = "org.springframework.data.jpa.repository.JpaRepository")
@ConditionalOnProperty(name = "zakat.repository.enabled", havingValue = "true")
public ZakatRepository zakatRepository() {
// => Auto-configured repository: only if JPA on classpath
return new JpaZakatRepository();
}
}
// => Register auto-configuration in META-INF/spring.factories
// src/main/resources/META-INF/spring.factories:
// org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
// com.example.zakat.autoconfigure.ZakatAutoConfigurationConsumers use starter by adding dependency:
<!-- User's pom.xml -->
<dependency>
<groupId>com.example</groupId>
<artifactId>zakat-spring-boot-starter</artifactId>
<version>1.0.0</version>
<!-- => Single dependency brings:
- zakat-calculator (core library)
- ZakatAutoConfiguration (auto-config)
- ZakatCalculator bean (auto-configured) -->
</dependency>
<!-- User's application.yml: configure via properties -->
zakat:
nisab-grams: 85
rate: 0.025
repository:
enabled: true # => Enable ZakatRepository auto-configurationGradle Starter Configuration
Gradle projects use Spring Boot plugin and dependency management:
// build.gradle: Gradle equivalent of Spring Boot starters
plugins {
id 'org.springframework.boot' version '3.2.2'
// => Spring Boot Gradle plugin:
// - Provides bootJar task (executable JAR)
// - Manages dependency versions
// - Configures Spring Boot plugin defaults
id 'io.spring.dependency-management' version '1.1.4'
// => Dependency management plugin:
// - Imports spring-boot-dependencies BOM
// - Provides version management without parent POM
}
dependencies {
// Spring Boot starters: no version required
implementation 'org.springframework.boot:spring-boot-starter-web'
// => Version resolved from spring-boot-dependencies BOM
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'org.postgresql:postgresql'
// => Runtime dependency: not needed for compilation
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// => Test scope: only available during testing
}
// Kotlin DSL (build.gradle.kts)
plugins {
id("org.springframework.boot") version "3.2.2"
id("io.spring.dependency-management") version "1.1.4"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("org.postgresql:postgresql")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}Overriding Managed Dependency Versions
Spring Boot BOM manages dependency versions, but you can override:
<!-- Override managed version in Maven -->
<properties>
<!-- Override Jackson version (Spring Boot default: 2.16.1) -->
<jackson.version>2.16.2</jackson.version>
<!-- => Spring Boot respects this property when resolving jackson-databind -->
<!-- Override Hibernate version -->
<hibernate.version>6.4.3.Final</hibernate.version>
</properties>
<dependencies>
<!-- Explicit version overrides BOM -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
<!-- => Explicit version takes precedence over managed version -->
</dependency>
</dependencies>// Override managed version in Gradle
ext['jackson.version'] = '2.16.2'
// => Spring Boot plugin respects this property
dependencies {
// Explicit version overrides BOM
implementation('com.fasterxml.jackson.core:jackson-databind:2.17.0')
// => Explicit version takes precedence
}Production warning: Override managed versions only when necessary (security patches, critical bugs). Spring Boot tests starters together—overriding risks runtime incompatibilities.
Starter Dependencies Best Practices
// => Use starters for standard Spring modules
// GOOD: Spring Boot starter includes compatible versions
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
// => Add only drivers/connectors explicitly
// GOOD: Driver not included in starters, must add explicitly
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
// => Avoid mixing Spring Boot and standalone Spring dependencies
// BAD: Version mismatch between Spring Boot and standalone Spring Data
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>3.3.0</version> <!-- Incompatible with Spring Boot 3.2.2 -->
</dependency>
// => Use dependency exclusions to remove unwanted transitive deps
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<!-- Exclude default Tomcat, use Jetty instead -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<!-- Add Jetty starter explicitly -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>When to Use Starters vs Manual Dependencies
Use Spring Boot starters:
- Standard Spring modules (Web, Data JPA, Security, Actuator, Redis, Kafka)
- Rapid development with auto-configuration
- Teams lacking Spring expertise (starters provide best practices)
- Greenfield projects following Spring Boot conventions
Use manual dependencies:
- Non-standard libraries without Spring Boot starters
- Existing projects with custom dependency management
- Precise version control requirements (regulatory compliance)
- Minimal JAR size critical (embedded systems, AWS Lambda)
Production recommendation: Use starters as default for Spring modules, add manual dependencies only for non-Spring libraries (AWS SDK, Apache POI, etc.).
Related Patterns
- Auto-Configuration - How starters trigger auto-configuration via @Conditional
- Application Properties - Configure starter behavior via application.yml
- Configuration Properties - Type-safe configuration for custom starters