Backend Development 18 min read

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.

Top Architect
Top Architect
Top Architect
Optimizing SpringBoot Startup Time: Diagnosing Bean Scanning and Initialization Bottlenecks

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

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

performanceCacheSpringBootAutoConfigurationBeanScanningJavaConfig
Top Architect
Written by

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.

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.