skip to content

How do you package a custom Spring Boot starter, and why is it conventionally split into an 'autoconfigure' module and a 'starter' module?

level: seniorimportance: should knowfreq 40%

answer

  1. autoconfigure module = code + imports file
  2. starter module = deps only, no code
  3. integration libs optional in autoconfigure
  4. naming: xxx-spring-boot-starter (third-party)
  5. add config + autoconfigure annotation processors

basics

~20 s

A starter is usually two modules: an 'autoconfigure' module holding the @AutoConfiguration classes and code, and a near-empty 'starter' module that just declares dependencies (the autoconfigure module plus the libraries it needs). Apps depend on the starter.

solid answer

~40 s

By convention a custom starter is two artifacts. The *autoconfigure* module (e.g. acme-spring-boot-autoconfigure) contains the @AutoConfiguration classes, @ConfigurationProperties, conditions, and the AutoConfiguration.imports file; it treats most integration libraries as optional so it can be depended on without dragging everything in. The *starter* module (acme-spring-boot-starter) contains essentially no code — just a pom/build listing dependencies: the autoconfigure module plus the concrete libraries needed for the feature to actually work. Applications depend only on the starter, getting both the code and the runtime deps transitively. The split lets consumers who want the auto-config logic but their own dependency versions use the autoconfigure module alone. Naming matters: third-party starters must use the pattern xxx-spring-boot-starter, never spring-boot-starter-xxx, which is reserved for official starters.

code

java · 18 lines
java
// acme-spring-boot-starter — build.gradle (NO source code, just deps)
// dependencies {
//   api project(":acme-spring-boot-autoconfigure")
//   api "com.acme:greeting-core:1.4.0"   // the real runtime lib
// }

// acme-spring-boot-autoconfigure — build.gradle
// dependencies {
//   implementation "org.springframework.boot:spring-boot-autoconfigure"
//   compileOnly    "com.acme:greeting-core:1.4.0"  // OPTIONAL: enables @ConditionalOnClass
//   annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
//   annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
// }

// Naming convention (third-party):
//   acme-spring-boot-autoconfigure   <- code
//   acme-spring-boot-starter         <- dependency bundle
// NOT spring-boot-starter-acme (reserved for official Spring starters)

go deeper

for a junior

Know that a starter is a dependency you add and the feature works; splitting is a nice-to-know detail.

for a middle

Can describe the two modules and that the starter is code-free dependencies.

for a senior

Explains the optional-dependency rationale, naming convention, and the annotation processors that improve metadata/startup.

for a principal

Weighs single vs two-module tradeoffs, BOM/version alignment, and consumer override scenarios when publishing platform libraries.

**What a 'starter' is.** A Spring Boot *starter* is a convenience dependency: adding one jar transitively pulls in everything needed for a feature — the auto-configuration code *and* the underlying libraries — so the user writes one line in their build and the feature works. **The two-module convention.** 1. **`acme-spring-boot-autoconfigure`** — the real code: `@AutoConfiguration` classes, `@ConfigurationProperties` types, custom `Condition`s, and `META-INF/spring/...AutoConfiguration.imports`. It depends on `spring-boot-autoconfigure`, and declares the integration libraries as **optional** (Maven `<optional>true</optional>` / Gradle `compileOnly` or `optional`) so that merely depending on it doesn't force those libraries onto everyone. This is what allows `@ConditionalOnClass` to matter — the class may or may not be present. 2. **`acme-spring-boot-starter`** — (almost) **no source code**. Its build file lists dependencies: the `autoconfigure` module *plus* the concrete libraries the feature genuinely needs at runtime. Applications depend on this. **Why split them?** Separation of concerns: the auto-config *logic* is decoupled from the *dependency bundle*. A consumer who wants the beans but needs to manage library versions themselves (or exclude some) can depend on the autoconfigure module directly. Spring Boot's own starters follow exactly this pattern (`spring-boot-autoconfigure` vs the many `spring-boot-starter-*`). For a simple internal library you *can* collapse both into one module, but the two-module form is the documented convention. **Naming rules (important and frequently tested).** Official Spring starters are named `spring-boot-starter-<name>` (e.g. `spring-boot-starter-web`). **Third-party** starters must NOT use that prefix; they use `<name>-spring-boot-starter` (e.g. `acme-spring-boot-starter`). This avoids collisions and makes ownership clear. The autoconfigure module correspondingly is `<name>-spring-boot-autoconfigure`. **Recommended extras in the autoconfigure module.** - Add `spring-boot-configuration-processor` (annotation processor) so IDE metadata (`META-INF/spring-configuration-metadata.json`) is generated for your `@ConfigurationProperties` — gives users autocomplete for your properties. - Add `spring-boot-autoconfigure-processor` to generate `META-INF/spring-autoconfigure-metadata.properties`, letting Spring Boot filter out non-matching auto-configs faster at startup (condition metadata evaluated without loading classes). **Build-time gotchas.** - Don't put a `@SpringBootApplication` or `@ComponentScan` in the starter — it would scan into consumers unexpectedly. - Keep the starter module dependency-only; putting code there breaks the convention and the reusability benefit. - Mark integration libs optional in autoconfigure or you defeat `@ConditionalOnClass` (everyone gets the lib). - Version alignment: consider importing `spring-boot-dependencies` BOM so your transitive versions line up with the consumer's Boot version. **When to use.** Two-module split for anything you publish for others (internal platform teams, open source). Single-module is fine for a throwaway or tiny internal helper, but know the canonical form for interviews.

  • Why are the integration libraries declared 'optional' in the autoconfigure module but as regular dependencies in the starter?
    Optional in autoconfigure keeps @ConditionalOnClass meaningful and lets consumers depend on the config logic without being forced to take those libs. The starter, whose job is 'make the feature work out of the box,' declares them as real transitive dependencies.
  • What's the naming rule for third-party starters and why does it exist?
    Use <name>-spring-boot-starter (e.g. acme-spring-boot-starter), never spring-boot-starter-<name>, which is reserved for official Spring Boot starters. It prevents namespace collisions and signals the artifact isn't maintained by the Spring team.

saying these in an interview costs you the question

  • Putting @AutoConfiguration code in the starter module
  • Naming a third-party starter spring-boot-starter-xxx
  • Making integration libs mandatory in the autoconfigure module
  • Adding @SpringBootApplication/@ComponentScan to a starter

context