Optimizing SpringBoot Startup Time: Analyzing Bean Scanning and Injection Bottlenecks
This article explains how to diagnose and dramatically reduce the long startup time of a SpringBoot service by profiling the SpringApplicationRunListener and BeanPostProcessor phases, trimming excessive component scanning, manually configuring beans with JavaConfig, and understanding the impact of SpringBoot's auto‑configuration mechanisms.
The author discovered that a SpringBoot micro‑service took 6‑7 minutes to start, mainly due to heavy bean‑scanning and bean‑injection phases. By inspecting SpringApplicationRunListener and BeanPostProcessor implementations, the performance hotspots were identified in the contextLoaded and started stages of the run method.
1. Diagnosing the Startup Delay
Using breakpoints and custom listeners, the author measured the time spent in each SpringApplicationRunListener callback (starting, environmentPrepared, contextPrepared, contextLoaded, started, running, failed). The longest intervals were between contextLoaded and started , which correspond to the refreshContext and afterRefresh calls that invoke AbstractApplicationContext#refresh .
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
public static ConfigurableApplicationContext run(String... args) {
// load listeners
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
// ... many steps omitted for brevity ...
listeners.started(context);
listeners.running(context);
return context;
}During the refresh, the invokeBeanFactoryPostProcessors method executes all registered BeanFactoryPostProcessor s, especially ConfigurationClassPostProcessor , which parses @Configuration , @ComponentScan , @Import , and @Bean annotations. The author found that scanning a large number of third‑party packages (e.g., UPM) caused the majority of the delay.
2. Monitoring Bean Initialization
To pinpoint slow beans, a custom BeanPostProcessor was added that records the timestamp before and after bean initialization. The implementation logs any bean whose initialization time exceeds a threshold.
@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;
}
}The profiler revealed a bean that took 43 seconds because it queried massive configuration data from the database and wrote it to Redis. It also showed many unnecessary beans from third‑party libraries that were being instantiated even though the service only needed a single feature.
3. Optimization Strategies
3.1 Reduce Scanning Paths – Replace broad scanBasePackages declarations with explicit JavaConfig @Bean definitions for required third‑party components (e.g., UpmResourceClient ). This eliminates the scanning of irrelevant packages and cuts startup time from ~7 minutes to ~40 seconds.
@Configuration
public class ThirdPartyBeanConfig {
@Bean
public UpmResourceClient upmResourceClient() {
return new UpmResourceClient();
}
}3.2 Speed Up Slow Bean Initialization – For beans that perform heavy I/O during construction, consider lazy initialization, asynchronous loading via a thread pool, or moving the work to a background task after the application is up.
4. Unexpected Cache Behaviour
After the above changes, the service’s Redis cache component stopped working. The reason was SpringBoot’s auto‑configuration: when the original cache package was removed from scanning, SpringBoot’s CacheAutoConfiguration created a default RedisCacheManager because the condition @ConditionalOnMissingBean(CacheManager.class) became true. The automatically generated manager differed from the custom one provided by the third‑party cache starter.
To keep the custom cache implementation while still using the starter mechanism, the author added a META-INF/spring.factories entry for the component’s own auto‑configuration class, allowing SpringBoot to import it without scanning the whole package.
# EnableAutoConfigurations
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxx.ad.rediscache.XxxAdCacheConfigurationUnderstanding the interplay between component scanning, BeanPostProcessor timing, and SpringBoot’s auto‑configuration is essential for large Java back‑end services that need fast startup and minimal resource consumption.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.