Optimizing SpringBoot Startup Time: Analyzing and Reducing Bean Scanning and Initialization Overhead
This article investigates why a SpringBoot service takes 6‑7 minutes to start, identifies the heavy cost of component scanning and bean post‑processing, demonstrates how to monitor each phase with custom SpringApplicationRunListener and BeanPostProcessor implementations, and presents practical JavaConfig and starter‑based solutions that cut startup time from minutes to seconds.
The author discovered that a SpringBoot project required 6‑7 minutes to expose its port during development, severely impacting productivity. By debugging the SpringApplicationRunListener and BeanPostProcessor mechanisms, the main bottlenecks were traced to the bean‑scanning and bean‑initialization phases.
1. Analyzing the startup run method
The application uses an internal micro‑service component XxBoot whose launch flow mirrors SpringBoot: constructing an ApplicationContext and invoking its run method. The relevant source code is shown below:
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
public static ConfigurableApplicationContext run(Class
[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}The run method internally creates SpringApplicationRunListener instances via SpringFactoriesLoader and calls their lifecycle callbacks ( starting , environmentPrepared , contextPrepared , contextLoaded , started , running , failed ).
Only one default implementation exists: EventPublishingRunListener , which forwards events to registered ApplicationListener s.
By adding a custom listener ( MySpringApplicationRunListener ) and registering it in META-INF/spring.factories , the author logged timestamps at each stage, revealing that the longest delay occurs between contextLoaded and started , i.e., during refreshContext and afterRefresh .
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
public MySpringApplicationRunListener(SpringApplication sa, String[] args) {}
@Override public void starting() { log.info("starting {}", LocalDateTime.now()); }
@Override public void environmentPrepared(ConfigurableEnvironment env) { log.info("environmentPrepared {}", LocalDateTime.now()); }
@Override public void contextPrepared(ConfigurableApplicationContext ctx) { log.info("contextPrepared {}", LocalDateTime.now()); }
@Override public void contextLoaded(ConfigurableApplicationContext ctx) { log.info("contextLoaded {}", LocalDateTime.now()); }
@Override public void started(ConfigurableApplicationContext ctx) { log.info("started {}", LocalDateTime.now()); }
@Override public void running(ConfigurableApplicationContext ctx) { log.info("running {}", LocalDateTime.now()); }
@Override public void failed(ConfigurableApplicationContext ctx, Throwable ex) { log.info("failed {}", LocalDateTime.now()); }
}2. Monitoring bean initialization time
The second optimization leverages BeanPostProcessor to record the time before and after each bean’s initialization. The implementation stores start timestamps in a concurrent map and prints the elapsed time after the bean is fully created.
@Component
public class TimeCostBeanPostProcessor implements BeanPostProcessor {
private Map
costMap = Maps.newConcurrentMap();
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) {
costMap.put(beanName, System.currentTimeMillis());
return bean;
}
@Override public Object postProcessAfterInitialization(Object bean, String beanName) {
if (costMap.containsKey(beanName)) {
long cost = System.currentTimeMillis() - costMap.get(beanName);
if (cost > 0) {
costMap.put(beanName, cost);
System.out.println("bean: " + beanName + "\ttime: " + cost);
}
}
return bean;
}
}Running this processor exposed a bean that took 43 seconds because it queried a massive amount of configuration data from the database and cached it in Redis. It also revealed many third‑party beans (e.g., UPM services) that were unnecessary for the current feature.
3. Optimization strategies
3.1 Reducing scan paths – Instead of scanning large packages, the author switched to explicit JavaConfig bean definitions. For example, the UPM client bean was manually defined:
@Configuration
public class ThirdPartyBeanConfig {
@Bean
public UpmResourceClient upmResourceClient() {
return new UpmResourceClient();
}
}This eliminated unrelated beans, lowered memory usage, and reduced the startup time to about 40 seconds.
3.2 Handling slow bean initialization – For beans that still required heavy work (e.g., loading metadata), the author suggested asynchronous execution or lazy loading.
3.3 Dealing with cache auto‑configuration – After removing the cache component’s scan path, SpringBoot still created a RedisCacheManager automatically because @EnableCaching triggers CacheAutoConfiguration . The auto‑configured manager is created only when no user‑defined CacheManager bean exists ( @ConditionalOnMissingBean(CacheManager.class) ).
To avoid the surprise, the author recommends using the starter mechanism: add a META-INF/spring.factories entry under EnableAutoConfiguration that points to the component’s own configuration class ( XxxAdCacheConfiguration ). This way the cache bean is imported automatically without scanning the whole package.
# EnableAutoConfigurations
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxx.ad.rediscache.XxxAdCacheConfigurationIn summary, by instrumenting SpringBoot’s lifecycle, pinpointing the expensive ConfigurationClassPostProcessor and component‑scan phases, and replacing broad classpath scanning with targeted JavaConfig and starter‑based auto‑configuration, the author reduced the local startup time from ~7 minutes to ~40 seconds while preserving functional correctness.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.