Backend Development 20 min read

Optimizing SpringBoot Application Startup Time: Diagnosis and Solutions

This article documents the diagnosis of slow SpringBoot startup in a large‑scale advertising platform and presents a series of optimizations—including actuator monitoring, Tomcat TLD scan disabling, HBase async warm‑up, custom BeanPostProcessor timing, asynchronous JSF consumer refer, Tomcat version tuning, and hardware migration—that together reduce launch time by about 60%.

JD Retail Technology
JD Retail Technology
JD Retail Technology
Optimizing SpringBoot Application Startup Time: Diagnosis and Solutions

The article begins by describing the problem: a SpringBoot 2.6.2 application used in an advertising platform takes 400‑500 seconds to start on a single machine, leading to long deployment cycles and slow rollbacks.

Three main pain points are identified: excessive deployment time, long container orchestration, and slow iterative testing, illustrated with a diagram of the original production cluster.

2. Solution Overview

Several methods for locating the startup bottleneck are introduced:

Using SpringBoot Actuator's startup endpoint to view bean initialization times (noting its aggregation limitation).

Analyzing debug startup logs to spot blank seconds indicating blocking points.

2.2.1 Tomcat TLD Scan Optimization

Tomcat scans JARs for .tld files, which is unnecessary for non‑JSP projects. Disabling this by setting tomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar in catalina.properties removes a 30‑second delay.

2.2.2 HBase Async Pre‑warm

During startup, HBase metadata loading caused a 6‑second pause. Implementing a SmartInitializingSingleton that pre‑warms HBase in a separate thread eliminates this block.

2.3 Custom BeanPostProcessor Timing

Two BeanPostProcessors are added to measure per‑bean initialization cost:

@Component
@Slf4j
public class TimeCostPriorityOrderedBeanPostProcessor implements BeanPostProcessor, PriorityOrdered {
    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 > 50) {
                log.info("Bean {} init cost: {} ms", beanName, cost);
            }
        }
        return bean;
    }
    @Override
    public int getOrder() { return Integer.MIN_VALUE; }
}

The second processor, ZLowOrderTimeCostBeanPostProcessor , records the after phase similarly.

Profiling revealed that beans annotated with @JsfConsumer incurred high latency due to synchronous ConsumerConfig.refer() calls.

2.3.1 Asynchronous JSF Consumer Refer

A dedicated thread pool executes the refer method, preventing it from blocking the main startup thread:

public static final ThreadPoolExecutor CONSUMER_REFER = new ThreadPoolExecutor(
        32, 32,
        60, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(102400),
        ThreadFactories.create("JSF-REFER"),
        new ThreadPoolExecutor.CallerRunsPolicy());

The consumer scanning logic submits each refer task to this pool and tracks completion with counters and futures.

2.3.2 Delayed ConsumerBean (Magic‑Modified)

A custom DelayConsumerBean returns a proxy object immediately and performs the actual refer asynchronously, ensuring the application can start while the proxy is being prepared:

public class DelayConsumerBean
extends ConsumerConfig
implements InitializingBean, FactoryBean
{
    public T getObject() throws Exception {
        T proxy = (T) Proxy.newProxyInstance(
                interfaceClass.getClassLoader(),
                new Class[]{interfaceClass},
                new DelayConsumerInvocationHandler());
        CONSUMER_REFER_EXECUTOR.submit(() -> super.refer());
        return proxy;
    }
    // InvocationHandler forwards calls to the real consumer once available
}

Additional Findings

Tomcat version differences (8.0.53 vs 8.5.42) affect bean instantiation speed due to class‑loading changes.

Running the application on newer, higher‑performance hardware reduces startup time by roughly 20%.

3. Summary of Optimizations and Impact

Disabled Tomcat TLD scanning.

Asynchronously pre‑warmed HBase.

Implemented custom BeanPostProcessors to identify slow beans.

Made JSF consumer creation asynchronous via custom BeanPostProcessor and delayed ConsumerBean.

Selected a faster Tomcat version.

Migrated to a high‑performance data center.

Combined, these measures improved the application startup speed by approximately 60%.

For further reading, the article links to related posts on lock failures and clean architecture practices.

backendjavaperformanceSpringBootBeanPostProcessorStartupOptimization
JD Retail Technology
Written by

JD Retail Technology

Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.

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.