Backend Development 21 min read

Optimizing SpringBoot Application Startup Time: Diagnosis, BeanPostProcessor Tweaks, and Asynchronous JSF Consumer Initialization

This article documents the systematic analysis and multi‑step optimization of a SpringBoot application's slow startup, covering profiling with Actuator, Tomcat TLD scan disabling, HBase async warm‑up, custom BeanPostProcessor timing, and asynchronous JSF consumer initialization to cut launch time by over 60 percent.

JD Tech Talk
JD Tech Talk
JD Tech Talk
Optimizing SpringBoot Application Startup Time: Diagnosis, BeanPostProcessor Tweaks, and Asynchronous JSF Consumer Initialization

1. Introduction

The article records the optimization measures taken before a major promotion to address the slow startup of a SpringBoot application, describing how to locate blocking points and solve them, using JD's internal JSF RPC framework (similar to Dubbo) on SpringBoot 2.6.2.

2. Problem Background

In production, a single machine of the advertising platform core application required 400‑500 seconds to start, plus about one minute for image download, resulting in a total deployment time close to ten minutes. This caused delayed rollbacks, prolonged recovery, and hindered continuous business operation.

With hundreds of containers, the overall deployment orchestration exceeded half an hour, and each compile‑plus‑deploy cycle in the pre‑release test environment took over ten minutes, severely affecting integration speed.

These issues highlighted the urgent need to improve startup speed.

3. Solution Overview

To investigate the slow startup, several diagnostic methods were employed:

3.1 Use SpringBoot Actuator

The Actuator provides endpoints such as /actuator/startup to view bean startup times, though bean‑level timings can be inaccurate due to cumulative loading.

3.2 Analyze Startup Logs

Enabling debug logs allows second‑by‑second inspection of blank seconds in the log to locate blocking points, though manual analysis becomes impractical for large projects.

3.2.1 Tomcat TLD Scan Optimization

Tomcat scans JARs for .tld files during startup, which is unnecessary for projects not using JSP. Disabling this scan by setting tomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar in catalina.properties removes the delay.

3.2.2 Asynchronous HBase Warm‑up

Initial HBase configuration caused a six‑second pause. By implementing SmartInitializingSingleton and moving the warm‑up to an asynchronous thread, the delay was eliminated.

3.3 Custom BeanPostProcessor Timing

The majority of startup time resides in bean post‑processing. Two custom BeanPostProcessor implementations were added to measure and log beans whose initialization exceeds 50 ms.

@Component
@Slf4j
public class TimeCostPriorityOrderedBeanPostProcessor implements BeanPostProcessor, PriorityOrdered {
    private Map
costMap = Maps.newConcurrentMap();
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        costMap.put(beanName, System.currentTimeMillis());
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (costMap.containsKey(beanName)) {
            Long start = costMap.get(beanName);
            long cost = System.currentTimeMillis() - start;
            if (cost > 50) {
                log.info("==>Before method time beanName:{}, cost:{}", beanName, cost);
            }
        }
        return bean;
    }
    @Override
    public int getOrder() { return Integer.MIN_VALUE; }
}

The second processor runs later without priority:

@Component
@Slf4j
public class ZBeanPostProcessor implements BeanPostProcessor {
    private Map
costMap = Maps.newConcurrentMap();
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        costMap.put(beanName, System.currentTimeMillis());
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (costMap.containsKey(beanName)) {
            Long start = costMap.get(beanName);
            long cost = System.currentTimeMillis() - start;
            if (cost > 50) {
                log.info("==>After method time beanName:{}, cost:{}", beanName, cost);
            }
        }
        return bean;
    }
}

Analysis revealed that beans annotated with @JsfConsumer were consistently slow because the JSF consumer creation performed a blocking refer() call.

3.3.1 Asynchronous @JsfConsumer Proxy Generation

To avoid blocking the main thread, the refer() call was moved to a dedicated thread pool:

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

// Inside scanConsumer
Future
referFuture = CONSUMER_REFER.submit(() -> {
    try {
        // ... set up field accessibility
        ConsumerConfig consumerConfig = this.parseAnnotationConsumer(jsfConsumerTemplate, field.getType());
        addFilters(beanName, consumerConfig);
        Object ref = consumerConfig.refer();
        if (ref != null) {
            if (!beanFactory.containsSingleton(field.getName())) {
                beanFactory.registerSingleton(field.getName(), consumerConfig);
            }
            Object fieldBean = beanFactory.getBean(field.getName());
            field.set(bean, fieldBean);
        }
    } finally {
        // restore accessibility and decrement counter
    }
});
REFER_FUTURE.add(referFuture);

A listener on ContextRefreshedEvent waits for all futures to complete before allowing the application to finish startup.

3.4 Modified JSF ConsumerBean (DelayConsumerBean)

For cases where JSF’s own ConsumerBean is used, a custom delayed version returns a proxy immediately and performs the real refer() asynchronously:

public class DelayConsumerBean
extends ConsumerConfig
implements InitializingBean, FactoryBean
, ApplicationContextAware, DisposableBean, BeanNameAware {
    private static final ThreadPoolExecutor CONSUMER_REFER_EXECUTOR = new ThreadPoolExecutor(
            32, 32, 60, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(102400),
            ThreadFactories.create("JSF-REFER-2"),
            new ThreadPoolExecutor.CallerRunsPolicy());
    private T object;
    @Override
    public T getObject() throws Exception {
        Class
consumerInterfaceClass = ClassLoaderUtils.forName(this.interfaceId);
        T delayConsumer = (T) Proxy.newProxyInstance(
                consumerInterfaceClass.getClassLoader(),
                new Class[]{consumerInterfaceClass},
                new DelayConsumerInvocationHandler());
        REFER_FUTURE_LIST.add(CONSUMER_REFER_EXECUTOR.submit(() -> super.refer()));
        object = CommonUtils.isUnitTestMode() ? null : delayConsumer;
        return object;
    }
    // InvocationHandler and waitForRefer omitted for brevity
}

4. Additional Observations

Different Tomcat versions (8.0.53 vs 8.5.42) exhibited varying bean instantiation speeds, with the newer version being slower, likely due to class‑loading changes.

Hardware also impacted startup: newer, higher‑performance machines and data centers reduced launch time by at least 20 %.

5. Summary and Results

Implemented measures:

Disabled Tomcat TLD scanning.

Asynchronously pre‑warmed HBase.

Introduced custom BeanPostProcessor timing and asynchronous @JsfConsumer proxy generation.

Analyzed Tomcat version effects.

Migrated to higher‑performance data centers.

After these optimizations, startup time dropped from 400‑500 seconds to roughly 130‑150 seconds, an improvement of over 60 %.

backendjavaPerformanceSpringBootTomcatasyncBeanPostProcessor
JD Tech Talk
Written by

JD Tech Talk

Official JD Tech public account delivering best practices and technology innovation.

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.