Skip to content
AyoKoding

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.ZakatAutoConfiguration

Consumers 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-configuration

Gradle 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.).

Last updated February 5, 2026

Command Palette

Search for a command to run...