Backend Development 18 min read

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.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Optimizing SpringBoot Startup Time: Analyzing Bean Scanning and Injection Bottlenecks

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.XxxAdCacheConfiguration

Understanding 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.

PerformanceOptimizationSpringBootAutoConfigurationBeanPostProcessorBeanScanningJavaConfig
Code Ape Tech Column
Written by

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

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.