Backend Development 20 min read

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.

Top Architect
Top Architect
Top Architect
Optimizing SpringBoot Startup Performance by Reducing Bean Scanning and Monitoring Bean Initialization

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

Result

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.

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