Optimizing SpringBoot Startup Performance by Reducing Bean Scanning and Monitoring Bean Initialization
This article explains how to diagnose and dramatically reduce SpringBoot application startup time—from several minutes to seconds—by analyzing the SpringApplicationRunListener and BeanPostProcessor phases, limiting component scanning, customizing bean registration, and handling cache auto‑configuration issues, with detailed code examples and performance measurements.
Background
In a typical SpringBoot project the service startup can take 6‑7 minutes, severely affecting developer productivity. The author investigated the SpringApplicationRunListener and BeanPostProcessor mechanisms and identified two major bottlenecks: bean scanning and bean injection.
Investigation of the Slow Startup
The run method of SpringApplication creates an ApplicationContext and triggers a series of listener callbacks defined in SpringApplicationRunListener . By placing breakpoints in the listener methods the author measured the time spent in each phase and found the longest delays between contextLoaded and started , which correspond to the refreshContext and afterRefresh calls.
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
public ConfigurableApplicationContext run(Class
[] primarySources, String[] args) {
// ...
listeners.starting();
// environment preparation
listeners.environmentPrepared(environment);
// context creation
listeners.contextPrepared(context);
// context loading
listeners.contextLoaded(context);
// after refresh
listeners.started(context);
// running
listeners.running(context);
// ...
}The refresh method of AbstractApplicationContext invokes all registered BeanFactoryPostProcessor implementations. The ConfigurationClassPostProcessor alone consumes most of the time because it parses @Configuration , @ComponentScan , @Import , and @Bean annotations across all scanned packages.
Monitoring Bean Injection Time
To pinpoint slow beans, the author implemented a custom BeanPostProcessor that records the timestamp before and after bean initialization and logs any bean whose initialization 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) {
Long start = costMap.get(beanName);
if (start != null) {
long cost = System.currentTimeMillis() - start;
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 to initialize because it queried a massive amount of configuration data from the database and wrote it to Redis.
Optimization Strategies
1. Reducing Scanning Paths
Instead of scanning large third‑party packages, the author removed unnecessary scanBasePackages entries from the main @SpringBootApplication annotation and registered required beans explicitly via JavaConfig.
@Configuration
public class ThirdPartyBeanConfig {
@Bean
public UpmResourceClient upmResourceClient() {
return new UpmResourceClient();
}
}This eliminates the registration of unrelated services and controllers, cuts down the number of scanned classes, and reduces memory consumption.
2. Handling Slow Bean Initialization
For beans that perform heavy work during construction, the author suggests either lazy loading or moving the work to a background thread (e.g., using an executor service) so that the startup path remains fast.
3. Dealing with Cache Auto‑Configuration
After the above changes the service failed to find the custom CacheManager . The reason was that SpringBoot’s own CacheAutoConfiguration created a default RedisCacheManager because the user‑provided bean was missing. The article explains the inner workings of @EnableAutoConfiguration , CacheConfigurationImportSelector , and the conditions that cause the auto‑configuration to kick in.
To make the custom cache component work without scanning its package, the author recommends turning it into a starter: add an entry to META-INF/spring.factories under EnableAutoConfiguration so that SpringBoot will import the component automatically.
# EnableAutoConfigurations
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxx.ad.rediscache.XxxAdCacheConfigurationResult
After applying the scanning reduction, bean‑initialization monitoring, and cache‑starter adjustments, the local startup time dropped from around 7 minutes to roughly 40 seconds, a dramatic improvement. The article also provides a checklist of remaining issues and best‑practice tips for large SpringBoot services.
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.