Skip to main content
Java

3 Annotations to Speed Up Spring Bean Startup

7 mins

A progress bar with a half-full indicator, symbolizing the gradual loading of Spring beans during application startup.

Why Startup Time Matters #

Most Spring Boot application are services that are long running. The framework is most commonly used for web applications, though it can also be used for batch jobs and command line applications.

When running on the command line, startup line can be critical, especially for short lived applications that may be called frequently.

Startup time may be less critical for long running web applications, but it can still impact deployment times and the ability to quickly roll out updates.

Docker and Kubernetes need fast startup times to scale up and down quickly.

However, for microservices architectures, especially in Docker or Kubernetes environments, where services may be spun up and down frequently according to demand, startup time becomes more important.

So improving the Spring bean startup time can improve developer productivity, reduce deployment times, and enhance the responsiveness of applications in dynamic environments.

There are various techniques to speed up Spring bean startup times, which we will explore in this article.

Lazy Initialization (@Lazy) #

For beans that are not needed at startup

When the Spring application context is loaded, classes annotated with @Component, @Service, @Repository, @Controller, and other stereotype annotations are scanned and instantiated as beans. This is known as eager initialization.

To disable this behavior, you can use various ways.

Using @Lazy Annotation #

Making all beans lazy may not be desirable. Therefore, you can use the @Lazy annotation to control lazy initialization on a per-bean basis.

For beans defined in a configuration class, you can add @Lazy to the @Bean method.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

@Configuration
public class AppConfig {
    @Bean
    @Lazy
    public MyBean myBean() {
        return new MyBean();
    }
}

Note ,that when using configuration classes, there is no need to annotate the bean class itself with @Component. If you do have both, the @Component will override the configuration class, and the bean will be started eagerly.

public class MyBean {
    public MyBean() {
        // Bean construction logic...
    }
}

If you are just using @Component annotation and not a configuration class, you can add @Lazy to the class itself.

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
@Lazy
public class MyBean {
    public MyBean() {
        // Bean construction logic...
    }
}

Lazy Injection #

A class that uses the annotation of @Component or a sub-class, such as @RestController, will be initialized eagerly by default along with all its dependencies.

Add @Lazy to the constructor parameter to make the dependency lazy.

import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class WebController {
    private MyBean myBean;

    public WebController(@Lazy MyBean myBean) {
        this.myBean = myBean;
    }
}

Using @Lazy with @Autowired #

If you are using the @Autowired annotation, you will need to add @Lazy to the bean reference and the bean itself.

@Service
public class MyService {
    @Autowired
    @Lazy
    private MyBean myBean;
}

Make All Beans Lazy #

Typically, when you have lots of beans, cherry-picking a handful of beans to make lazy may not improve startup time significantly.

An alternative approach would be to make all beans lazy by default, and then make specific beans eager as needed.

To enable lazy initialization for all beans, you can set the following property in your application.properties or application.yml file:

spring.main.lazy-initialization=true

Then disable lazy initialization for specific beans by adding @Lazy(false) to the bean definition.

@Configuration
public class AppConfig {
    @Bean
    @Lazy(false)
    public MyEagerBean myEagerBean() {
        return new MyEagerBean();
    }
}

Asynchronous Bean Initialization (@Async) #

For beans that are needed at startup, but take a long time to initialize

The problem with lazy initialization is that it only delays the initialization of beans until they are needed, but it does not speed up the overall startup time if many beans are needed at startup.

Additionally, delaying initialization can lead to performance issues later when the bean is first accessed, as the initialization will occur at that time, potentially causing delays in request handling.

The solution is to initialize beans asynchronously, so that multiple beans can be initialized in parallel, reducing the overall startup time.

This can be achieved using @Async annotation and @EnableAsync on a configuration class.

First enable async processing in your configuration or main application class.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class MyApp {}

Then use the @Async annotation on bean startup methods, or on methods that perform time consuming initialization tasks.

Using @Async with @EventListener #

You can use @Async on any method that performs a time consuming task, such as loading data from a database or an external service.

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component
public class DataPreloader {
    private static final Logger logger = LoggerFactory.getLogger(DataPreloader.class);

    @EventListener(ApplicationReadyEvent.class)
    @Async
    public void preloadData() {
        logger.info("Preloading data on thread: {}", Thread.currentThread().getName());

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        logger.info("Data preloaded!");
    }
}

When you run the application, you will see that the data preloading happens on a separate thread, allowing the application to start up faster. Look for the thread name in the log output, where the spring application is started on the main thread, and the data preloading happens on a different thread, such as task-1.

INFO  [springproj] [  main] SpringprojApplication   : Started SpringprojApplication in 0.657 seconds
INFO  [springproj] [task-1] DataPreloader           : Preloading data on thread: task-1

Using @Async with @PostConstruct #

Adding Async to a @PostConstruct method will not work. As per the Spring documentation on @Async:

You can not use @Async in conjunction with lifecycle callbacks such as @PostConstruct. To asynchronously initialize Spring beans, you currently have to use a separate initializing Spring bean that then invokes the @Async annotated method on the target bean.
Spring Documentation

This means that you will need to create a separate bean that will call the async method on the target bean.

public class SampleBeanImpl implements SampleBean {

	@Async
	void doSomething() {
		// ...
	}

}

public class SampleBeanInitializer {

	private final SampleBean bean;

	public SampleBeanInitializer(SampleBean bean) {
		this.bean = bean;
	}

	@PostConstruct
	public void initialize() {
		bean.doSomething();
	}

}

Profile-Based Bean Loading (@Profile) #

For beans that are only needed in certain environments or configurations

Profiles allow you to define different beans for different environments and requirements.

You can use the @Profile annotation to specify that a bean should only be created for a specific profile.

For example, you may have multiple storage implementation, Amazon S3, Google Cloud Storage, and local file system. Setting up a storage service may involve initializing connections, loading credentials, and other setup tasks that are time-consuming.

You can define beans for each implementation and use profiles to load only the relevant one.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
public class StorageConfig {

    @Bean
    @Profile("aws")
    public StorageService awsStorageService() {
        return new AwsStorageService();
    }

    @Bean
    @Profile("gcp")
    public StorageService gcpStorageService() {
        return new GcpStorageService();
    }

    @Bean
    @Profile("local")
    public StorageService localStorageService() {
        return new LocalStorageService();
    }
}

With this setup, only the required storage service bean will be created based on the active profile, reducing startup time.

To specify which profile to use, you can use the following methods. Note that multiple profiles can be specified, separated by commas.

In application.properties #

spring.profiles.active=aws

As a Command Line Argument #

java -jar myapp.jar --spring.profiles.active=aws

As an Environment Variable #

export SPRING_PROFILES_ACTIVE=aws

Summary #

Just by placing a some strategic annotations, you can significantly speed up Spring bean startup times, with minimal code changes.

The table below summarizes the techniques to speed up Spring bean startup times.

Technique Description Use Case
Lazy Initialization Delays bean creation until first use. Beans that are not needed at startup.
Asynchronous Initialization Initializes beans in parallel using @Async. Beans that are needed at startup but take a long time to initialize.
Profile-Based Loading Loads beans based on active profiles using @Profile. Beans that are only needed in certain environments or configurations.

Of course, you will need to understand what behaviour you want, and test to ensure that the changes do not impact functionality or user experience.