How to Cut Spring Boot Startup Time from 7 Minutes to 40 Seconds
This article explains why a SpringBoot service took 6‑7 minutes to start, shows how to pinpoint the bottlenecks using SpringApplicationRunListener and BeanPostProcessor, and presents concrete optimizations—reducing scan paths, manually registering beans, and fixing cache auto‑configuration—to shrink the local startup time to about 40 seconds.
Background
During daily development a SpringBoot project took 6‑7 minutes to expose its port. By debugging
SpringApplicationRunListenerand
BeanPostProcessorit was discovered that the bean‑scanning and bean‑injection phases caused the major slowdown.
The following knowledge points are covered:
Observing the SpringBoot
runmethod via
SpringApplicationRunListener;
Monitoring bean‑injection time with
BeanPostProcessor;
SpringBoot cache auto‑configuration principles;
Starter‑based auto‑configuration and custom starter creation.
Investigating Startup Time
Two main investigation directions were identified:
Trace the SpringBoot service startup process;
Trace bean‑initialization time.
Observing the SpringBoot run Method
The project uses an internal micro‑service component
XxBootwhose startup flow mirrors SpringBoot: constructing an
ApplicationContextand then invoking its
runmethod.
<code>public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }</code>The
runmethod is divided into several stages defined by the
SpringApplicationRunListenerinterface:
<code>public interface SpringApplicationRunListener { void starting(); void environmentPrepared(ConfigurableEnvironment env); void contextPrepared(ConfigurableApplicationContext ctx); void contextLoaded(ConfigurableApplicationContext ctx); void started(ConfigurableApplicationContext ctx); void running(ConfigurableApplicationContext ctx); void failed(ConfigurableApplicationContext ctx, Throwable ex); }</code>SpringBoot provides a single default implementation
EventPublishingRunListenerthat publishes events at each stage. By adding a custom implementation of
SpringApplicationRunListenerand registering it in
META-INF/spring.factories, timestamps can be logged at the end of each stage to locate the most time‑consuming phases.
Monitoring Bean Initialization Time
The
BeanPostProcessorinterface allows hooking before and after bean initialization. By recording the start time in
postProcessBeforeInitializationand computing the elapsed time in
postProcessAfterInitialization, the initialization cost of each bean can be measured.
<code>@Component public class TimeCostBeanPostProcessor implements BeanPostProcessor { private Map<String, Long> 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) { costMap.put(beanName, cost); System.out.println("bean: " + beanName + "\ttime: " + cost); } } return bean; } }</code>Running this processor revealed a bean that took 43 seconds because it queried a large amount of configuration data from the database and wrote it to Redis.
Optimization Strategies
How to Reduce Excessive Scan Paths
Instead of scanning large third‑party packages, explicitly register only the required beans via JavaConfig. For example, replace the scan of
com.xxx.ad.upmwith a configuration class that defines the needed
UpmResourceClientbean:
<code>@Configuration public class ThirdPartyBeanConfig { @Bean public UpmResourceClient upmResourceClient() { return new UpmResourceClient(); } }</code>This eliminates unrelated beans (services, controllers) from the application context, reduces memory usage, and shortens startup time.
How to Address Slow Bean Initialization
For beans that perform heavy work during initialization, consider asynchronous execution or lazy loading. In the example, the slow bean that loads configuration data could be refactored to load data in a background thread after the application has started.
Understanding SpringBoot Cache Auto‑Configuration
SpringBoot’s
@EnableAutoConfigurationimports a series of auto‑configuration classes listed in
META-INF/spring.factories. The cache subsystem is configured by
CacheAutoConfiguration, which imports
CacheConfigurationImportSelector. This selector chooses one concrete cache configuration based on the available dependencies; in the project it selects
RedisCacheConfiguration, which creates a
RedisCacheManager.
If a custom cache component’s package is removed from the scan, SpringBoot still creates its own
RedisCacheManagerbecause the auto‑configuration class is annotated with
@ConditionalOnMissingBean(CacheManager.class). Therefore the missing custom bean is silently replaced by the default one.
Using a Starter for the Cache Component
To make the custom cache component “plug‑and‑play”, add a
META-INF/spring.factoriesentry under
org.springframework.boot.autoconfigure.EnableAutoConfigurationthat points to the component’s configuration class (e.g.,
com.xxx.ad.rediscache.XxxAdCacheConfiguration). This lets SpringBoot import the custom configuration automatically without scanning the whole package.
Result
After applying the above optimizations the local startup time dropped from roughly 7 minutes to about 40 seconds, a dramatic improvement.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
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.