Optimizing SpringBoot Startup Time: Diagnosing Bean Scanning and Initialization Bottlenecks
This article explains how to identify and reduce the excessive startup latency of a SpringBoot service by profiling the run method, customizing SpringApplicationRunListener and BeanPostProcessor, trimming component‑scan paths, and leveraging JavaConfig and starter mechanisms to avoid unnecessary bean loading and auto‑configuration overhead.
In a typical SpringBoot project the service startup can take 6‑7 minutes, mainly due to heavy bean‑scanning and bean‑initialization phases. By debugging the SpringApplicationRunListener and BeanPostProcessor mechanisms, the author pinpointed the costly stages within the run method.
1. Profiling the run method
The run method creates an ApplicationContext , triggers a series of listeners (starting, environmentPrepared, contextPrepared, contextLoaded, started, running, failed) defined in SpringApplicationRunListener . Adding a custom listener that logs timestamps at each stage reveals that most time is spent between contextLoaded and started , i.e., during refreshContext and afterRefresh .
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
public ConfigurableApplicationContext run(String... args) {
// ...
listeners.starting();
// environment preparation
listeners.environmentPrepared(environment);
// context creation
listeners.contextPrepared(context);
// context loading
listeners.contextLoaded(context);
// refresh
refreshContext(context);
// after refresh
afterRefresh(context);
listeners.started(context);
// runners
listeners.running(context);
return context;
}The refreshContext ultimately calls AbstractApplicationContext.refresh , where the ConfigurationClassPostProcessor parses @Configuration , @ComponentScan , @Import , and @Bean annotations. Scanning large third‑party packages (e.g., UPM) and processing many @ComponentScan base packages cause the majority of the delay.
2. Monitoring bean initialization
Implementing a BeanPostProcessor that records the time before and after bean creation allows measurement of each bean's initialization cost.
public interface BeanPostProcessor {
default Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
default Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
@Component
public class TimeCostBeanPostProcessor implements BeanPostProcessor {
private Map
costMap = new ConcurrentHashMap<>();
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
costMap.put(beanName, System.currentTimeMillis());
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
Long start = costMap.get(beanName);
if (start != null) {
long cost = System.currentTimeMillis() - start;
if (cost > 0) {
System.out.println("bean: " + beanName + "\ttime: " + cost);
}
}
return bean;
}
}Running this processor exposed a bean that took 43 seconds because it queried massive configuration data from the database and wrote it to Redis.
3. Optimization strategies
3.1 Reduce scan paths – replace broad @ComponentScan with explicit JavaConfig beans. For the UPM client, instead of scanning com.xxx.ad.upm , define a dedicated configuration class:
@Configuration
public class ThirdPartyBeanConfig {
@Bean
public UpmResourceClient upmResourceClient() {
return new UpmResourceClient();
}
}3.2 Speed up heavy bean initialization – move expensive work to asynchronous tasks or lazy‑load the bean.
3.3 Cache auto‑configuration pitfall – after removing the cache component’s scan path, SpringBoot still created a RedisCacheManager via its own CacheAutoConfiguration . Because @EnableCaching was present, the auto‑configuration class RedisCacheConfiguration (conditioned on missing CacheManager ) supplied a default manager, masking the missing custom cache bean.
To ensure the intended cache manager is used, the component should be packaged as a starter and registered in META-INF/spring.factories :
# EnableAutoConfigurations
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxx.ad.rediscache.XxxAdCacheConfigurationThis way the custom configuration is imported automatically without scanning the whole package.
After applying the above changes the local startup time dropped from ~7 minutes to ~40 seconds, and the service behaved correctly in pre‑release testing.
The article concludes with a call for discussion, a QR‑code for a “Top‑Level Architect” community, and several promotional links.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn 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.