How would you author your own reusable starter for an internal library, and what are the conventions?
answer
- Two modules: autoconfigure + starter
- AutoConfiguration.imports file (not spring.factories in Boot 3)
- @AutoConfiguration + @ConditionalOnClass/OnMissingBean
- Name: acme-...-spring-boot-starter (prefix reserved)
- configuration-processor for metadata
basics
~20 sSplit it in two: an autoconfigure module with @AutoConfiguration classes (registered in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports), and a thin starter module that just depends on the autoconfigure module plus required libraries. Name it acme-spring-boot-starter (name first, not the reserved spring-boot-starter- prefix).
solid answer
~40 sThe Spring Boot convention is **two modules**. (1) An **autoconfigure module** containing `@AutoConfiguration` classes guarded by `@ConditionalOnClass`/`@ConditionalOnMissingBean`/`@ConditionalOnProperty`, plus optional `@ConfigurationProperties`. You register the auto-config classes by listing their fully-qualified names in `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` (Boot 2.7+/3.x; older Boot used `spring.factories` under the `EnableAutoConfiguration` key). (2) A **starter module** that contains essentially no code — its POM depends on the autoconfigure module and on the third-party libraries needed at runtime. Consumers add only the starter. **Naming:** never use the reserved `spring-boot-starter-` prefix; use `acme-spring-boot-starter`. Use `@ConditionalOnMissingBean` so consumers can override your beans, and mark `spring-boot-autoconfigure` and `spring-boot-configuration-processor` appropriately so metadata is generated for IDE hints.
code
java · 23 lines// --- autoconfigure module ---
@AutoConfiguration
@ConditionalOnClass(GreetingClient.class)
@EnableConfigurationProperties(GreetingProperties.class)
public class GreetingAutoConfiguration {
@Bean
@ConditionalOnMissingBean // let consumers override
GreetingClient greetingClient(GreetingProperties props) {
return new GreetingClient(props.baseUrl());
}
}
@ConfigurationProperties("acme.greeting")
record GreetingProperties(String baseUrl) {}
// src/main/resources/META-INF/spring/
// org.springframework.boot.autoconfigure.AutoConfiguration.imports
// contents (one FQN per line):
// com.acme.greeting.GreetingAutoConfiguration
// --- starter module (acme-greeting-spring-boot-starter) ---
// pom depends on the autoconfigure module + acme-greeting-core; no code.go deeper
Aware that you can package reusable config as a starter.
Knows the autoconfigure/starter split and the naming convention exists.
Can build both modules, register via AutoConfiguration.imports, and use the conditional/override annotations correctly.
Designs an internal starter ecosystem with stable override contracts, ordering, config-metadata, and governance of the module-naming/BOM conventions across teams.
## Why two modules Spring Boot deliberately separates **what auto-configures** from **what pulls dependencies**: - **autoconfigure module** — holds the wiring logic. Depends only on what it needs to compile (often with `optional`/`compileOnly` deps so its conditionals compile without forcing them on everyone). - **starter module** — a near-empty jar; its POM lists the runtime dependencies (the autoconfigure module + the third-party libs). This is the coordinate consumers actually add. Boot's own starters follow this: `spring-boot-starter-xxx` (deps) vs `spring-boot-autoconfigure` (logic). For a small internal lib you *can* collapse them into one, but the two-module split is the documented best practice. ## The autoconfigure module ```java @AutoConfiguration @ConditionalOnClass(GreetingClient.class) @EnableConfigurationProperties(GreetingProperties.class) public class GreetingAutoConfiguration { @Bean @ConditionalOnMissingBean // consumer can override public GreetingClient greetingClient(GreetingProperties props) { return new GreetingClient(props.getBaseUrl()); } } ``` Key annotations: - **`@AutoConfiguration`** (Boot 2.7+) marks the class and controls ordering (`before`/`after`). Replaces the old `@Configuration` + `spring.factories` style. - **`@ConditionalOnClass`** — only apply if a marker class is present (so a consumer without the lib isn't affected). - **`@ConditionalOnMissingBean`** — back off if the consumer already defined the bean. This is the *override contract* — critical for good starters. - **`@ConditionalOnProperty`** — enable/disable via config. - **`@EnableConfigurationProperties`** + a `@ConfigurationProperties("acme.greeting")` class for typed config. ## Registration (the crucial file) Boot discovers your auto-config via a resource file — **not** component scanning: ``` src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ``` containing one FQN per line: ``` com.acme.greeting.GreetingAutoConfiguration ``` **Legacy (Boot < 2.7):** `META-INF/spring.factories` with `org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.acme.greeting.GreetingAutoConfiguration`. Boot 3 removed support for the old key for auto-configuration — use the `.imports` file. ## The starter module POM ```xml <artifactId>acme-greeting-spring-boot-starter</artifactId> <dependencies> <dependency> <groupId>com.acme</groupId> <artifactId>acme-greeting-spring-boot-autoconfigure</artifactId> </dependency> <dependency> <!-- the actual runtime lib --> <groupId>com.acme</groupId> <artifactId>acme-greeting-core</artifactId> </dependency> </dependencies> ``` ## Naming convention (asked often) - **Reserved:** `spring-boot-starter-*` — only for official Spring modules. Do not squat it. - **Yours:** put your identifier first: `acme-greeting-spring-boot-starter`. Same idea for the autoconfigure module: `acme-greeting-spring-boot-autoconfigure`. ## Metadata for IDE hints Add `spring-boot-configuration-processor` (annotationProcessor) so your `@ConfigurationProperties` generate `META-INF/spring-configuration-metadata.json`, giving auto-complete/docs for your `acme.greeting.*` keys in `application.yml`. ## Gotchas - Auto-config classes are found via the `.imports` file, **not** `@ComponentScan` — a common failure is forgetting the file, so nothing wires. - Don't put `@ComponentScan` in an auto-config class; it can pull unintended beans from the consumer's package space. - Always use `@ConditionalOnMissingBean` for beans you expect consumers to override. - Order matters: use `@AutoConfiguration(after = ...)` when you depend on another auto-config's beans (e.g., DataSource). - Keep the starter module code-free; logic belongs in autoconfigure.
- Where do you register an auto-configuration class so Spring Boot picks it up in Boot 3?In src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, one fully-qualified class name per line. The old spring.factories EnableAutoConfiguration key is no longer supported for auto-config in Boot 3.
- Why split into an autoconfigure module and a separate starter module?Separation of concerns: the autoconfigure module holds conditional wiring logic (and can keep libs optional), while the starter is a code-free dependency aggregator consumers add. It mirrors Spring's own spring-boot-autoconfigure vs spring-boot-starter-* structure and lets people take the auto-config without the opinionated deps if needed.
- How do you let a consumer override a bean your starter defines?Annotate your @Bean with @ConditionalOnMissingBean so your definition backs off when the consumer declares their own bean of that type.
saying these in an interview costs you the question
- Registering auto-config via @ComponentScan instead of the AutoConfiguration.imports file
- Using the reserved spring-boot-starter- prefix for a custom/third-party starter
- Using spring.factories EnableAutoConfiguration key in Boot 3 (removed for auto-config)
- Forgetting @ConditionalOnMissingBean so consumers can't override beans