Interviewers like this question because it reveals whether you can turn a reusable feature into a one-line dependency instead of copying setup into every app.
Question: What is a custom Spring Boot starter?
Answer: A custom starter is a packaged way to add a feature to many Spring Boot apps with very little code in each app. It usually combines an auto-configuration module, default beans, and a starter dependency that pulls in the right libraries. Spring Boot creates the defaults only when the needed classes exist and the application has not already provided its own bean.
Interview-Ready Answer: I would say a custom Spring Boot starter is a reusable package that lets me add a feature with one dependency instead of hand wiring everything. In practice, I split it into a starter artifact that brings in dependencies and an auto-configuration jar that creates beans with conditions like @ConditionalOnClass and @ConditionalOnMissingBean. That gives safe defaults, but still lets the application override them cleanly. In Spring Boot 3, the auto-configuration class is usually registered through META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
A custom Spring Boot starter is not magic; it is a packaging pattern. The starter gives the consumer a single dependency, while the auto-configuration module decides what beans to create based on the classpath, properties, and existing user beans. Think of it as a safe default kit: it helps every app behave the same way without forcing one app-specific design on everyone.
@Configuration or @AutoConfiguration creates beans only when conditions match. Common conditions are @ConditionalOnClass for classpath presence, @ConditionalOnProperty for opt-in or opt-out behavior, and @ConditionalOnMissingBean for back-off.META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. In Boot 2.7 and earlier, the older mechanism was META-INF/spring.factories.| Option | Contains | Best for | Gotcha |
|---|---|---|---|
| Custom starter | Deps + auto-config | One-line adoption | Needs metadata |
| Auto-config module | Bean wiring | Default behavior | Not enough alone |
| Plain library | Utility code | Shared helpers | No auto wiring |
Use a custom starter when many services need the same setup and you want them to start with sane defaults: logging, tracing, messaging, security helpers, clients, or SDK wrappers. Do not use a starter for one tiny app-specific feature; that just hides complexity in another jar. The real win is consistency: every service gets the same default behavior, while each app can still override it with its own bean or property.
The cost is mostly at startup, not at request time. Condition checks are roughly O(n) in the number of auto-configuration classes, but each check is small and the runtime cost after the beans are created is just normal Spring bean usage. Keep bean creation cheap: do not open sockets, hit databases, or call remote services in a starter constructor. Also remember that @ConditionalOnProperty defaults to off unless you set matchIfMissing = true, and proxyBeanMethods = false is often used to reduce startup overhead when configuration methods do not call each other.
Real edge cases: if you forget @ConditionalOnMissingBean, your starter may override user beans by accident; if you forget @ConditionalOnClass, the app can fail on startup when a third-party library is absent; and if one starter depends on another, you may need ordering hints so Boot evaluates them in the right sequence. A good mental rule is: defaults should be helpful, never bossy.
Version note: Spring Boot 3 favors the new imports file, which is clearer and AOT-friendly. Boot 2.7 and older used spring.factories; interviewers like to hear that you know both, because many older codebases still use the old style.
Real-World Story: A checkout service at an e-commerce company used an internal payments-spring-boot-starter to wire a gateway client, retry policy, and metrics. A developer removed @ConditionalOnClass by mistake, so the payment bean tried to load on services that did not even ship the gateway SDK. Deploys started failing with NoClassDefFoundError and UnsatisfiedDependencyException, pods went into CrashLoopBackOff, and users saw checkout time out until the release was rolled back.
What went wrong: The team treated the starter like ordinary app code instead of guarded infrastructure. The symptoms were startup failures, repeated restart logs, and missing checkout traffic; the fix was to restore the classpath condition and add a test that boots the app with the dependency removed.
package com.example.demo;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
@SpringBootApplication
// In a real starter, the auto-configuration would live in a separate jar and be
// listed under META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
// We import it here only so the example can run as one file.
@Import({CustomGreetingConfiguration.class, GreetingStarterAutoConfiguration.class})
public class CustomStarterDemoApplication {
public static void main(String[] args) {
SpringApplication.run(CustomStarterDemoApplication.class, args);
}
@Bean
CommandLineRunner demo(ObjectProvider<GreetingService> greetingServiceProvider) {
return args -> {
// ObjectProvider avoids a hard startup failure when the starter is disabled.
GreetingService service = greetingServiceProvider.getIfAvailable();
if (service == null) {
System.out.println("No GreetingService bean. Run with --app.greeting.enabled=true to enable the starter.");
return;
}
System.out.println(service.greet("Ava"));
System.out.println("Active implementation: " + service.getClass().getSimpleName());
};
}
interface GreetingService {
String greet(String name);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "app.greeting", name = "enabled", havingValue = "true", matchIfMissing = true)
static class GreetingStarterAutoConfiguration {
@Bean
@ConditionalOnMissingBean
GreetingService greetingService(Environment environment) {
// This is the safe default. If the application defines its own GreetingService,
// @ConditionalOnMissingBean makes Boot back off instead of overwriting it.
String prefix = environment.getProperty("app.greeting.prefix", "Hello");
return name -> prefix + ", " + name + "!";
}
}
@Profile("custom")
@Configuration(proxyBeanMethods = false)
static class CustomGreetingConfiguration {
@Bean
GreetingService customGreetingService() {
return name -> "Ahoy, " + name + "!";
}
}
}
Follow-up & Tricky Questions:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. The starter module itself is often just a thin dependency wrapper.@ConditionalOnMissingBean. That makes the starter back off when the app defines its own bean of the same type.@ConditionalOnProperty and turn the property off. This is better than forcing consumers to exclude classes or edit internal code.ApplicationContextRunner or a small Boot test to check the bean appears with the right classpath and disappears when the property is off. You should always test both the enabled path and the back-off path.@ConditionalOnMissingBean, the app wins and the starter backs off. Without that condition, the starter may override the app or cause ambiguity.@ConditionalOnClass. That condition is one of the main safety rails that keeps optional integrations from breaking unrelated apps.Common Mistakes:
@ConditionalOnClass, @ConditionalOnProperty, and @ConditionalOnMissingBean so the starter is safe and optional.Memory Hook: Think of a custom starter as a power strip: the starter POM brings the outlets, auto-configuration flips the switch, and @ConditionalOnMissingBean lets the user unplug your lamp and plug in their own.
Cheat Sheet:
AutoConfiguration.imports; Boot 2.7 used spring.factories.@ConditionalOnMissingBean so apps can override defaults.@ConditionalOnClass to protect optional integrations.Practice Tasks:
Clock bean with a configurable timezone.