How to use Lazy annotation in Spring Boot

In this article, let us discuss about lazy initialization and how do we delay creating dependencies until when needed in Spring Boot

Introduction

Dependency Injection is a pattern where required dependencies of a class in an application are provided to the class from the outside, without having to use the new keyword. The IoC Container, takes care of the initialization and object lifecycle management, so that the classes are loosely coupled.

In the previous articles we have seen different ways in which we can inject dependencies into our Spring Boot application – via Constructor, Setter or via Fields. We have also seen how to dynamically inject dependencies, for cases when the dependencies need to be resolved dynamically.

Although Dependency Injection is useful, sometimes we may encounter usecases where creating and maintaining objects of dependencies can be costly and inefficient.

Sometimes we may even encounter cases where although there are several dependencies registered, only few of them could be used.

But creating beans of all the dependencies as soon as the application starts up, even when we know that not all of them are going to be used, may not make sense.

What if we are somehow able to load dependencies not at the startup but only when they are needed? That way we can keep our application memory at check and speed up the startup time.

In this article, let us discuss lazy loading dependencies in spring boot and its implications.

What is Lazy Initialization?

Lazy Initialization is a technique in which the instantiation of the dependencies are delayed until they are first requested by any component. This means that although we register a class or a component as a Bean in the Spring Boot Dependency Injection system, the Container will not create an object of these until and unless some class or component requests for it.

This is opposite to the default approach, where all the registered beans are instantiated as soon as the main method annotated with @SpringBootApplication is called. This is called Eager Loading.

The main benefit of using Lazy loading is that not all the dependencies are initialized during bootstrapping. This means the application starts up quickly, has a lesser memory footprint – because not all the objects are created eagerly.

But how do we configure a bean to be loaded lazily? In Spring Boot, you have two approaches –

  1. Using the @Lazy annotation for Individual Beans
  2. Using application properties for Lazy Initialization Globally

Lazy loading dependencies using @Lazy annotation

Spring Boot provides a special annotation called @Lazy. This annotation goes over any Spring Bean, and can be specified wherever Beans are configured or injected. When Spring Boot comes across this annotation, it delays the instantiation of the Bean and only creates an object when it is needed by the calling class.

Using Lazy annotation at Bean level

To demonstrate this, let us consider BlahService, which is a dependency needed for FooService. We will annotate the service with Lazy annotation to load it lazily

package com.example.hello.services.utils;

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

@Service
@Lazy
public class BlahService {
   public BlahService() {
       System.out.println("Constructing BlahService");
   }
   public String askBlah() {
       return "Hello there! This is Blah!";
   }
}

Now this means that BlahService is not initialized eagerly and will be delayed until askBlah is called in a requesting class.

Using Lazy annotation during Injection

Alternatively, we can also use @Lazy annotation at an injection point – meaning the place where we are injecting the dependency into the required component.

package com.example.hello.services.utils;

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

@Service
public class FooService {
   private final BlahService blahService;
   
   public FooService(@Lazy BlahService blahService) {
       this.blahService = blahService;
       System.out.println("Constructing FooService");
   }

   public String getFoo() {
       return "Hello there! This is Foo!";
   }

   public String askBlah() {
       return blahService.askBlah();
   }
}

Using this approach, we don’t need to mark the entire component as lazy and instead mark specific dependencies as lazy. Otherwise, the entire FooService needs to be marked Lazy to have the entire chain of dependencies be loaded lazily.

My FooController is also marked @Lazy, because by default controllers are loaded eagerly and hence we need to ensure that all the sub dependencies are also loaded lazily.

package com.example.hello.controllers;

import com.example.hello.services.utils.BlahService;
import com.example.hello.services.utils.FooService;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Lazy
@RestController
@RequestMapping("/api/foo")
public class FooController {

   private final BlahService blahBean;
   private final FooService fooBean;
   
   public FooController(BlahService blahBean, FooService fooBean) {
       this.blahBean = blahBean;
       this.fooBean = fooBean;
       System.out.println("Constructing FooController");
   }

   @GetMapping("/getFoo")
   public String getFoo() {
       return fooBean.getFoo();
   }

   @GetMapping("/askBlah")
   public String askBlah() {
       return blahBean.askBlah();
   }
}

Using Lazy annotation at Bean Configuration

We can also mark Configuration Beans as Lazy, so that all the beans created in such manner are loaded lazily

package com.example.hello.config;

import com.example.hello.services.utils.BlahService;
import com.example.hello.services.utils.FooService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

@Configuration
@Lazy
public class LazyBeansConfig {
   public LazyBeansConfig() {
       System.out.println("Constructing LazyBeansConfig");
   }

   @Bean("blahBean")
   public BlahService blahBean() {
       return new BlahService();
   }

   @Bean("fooBean")
   public FooService fooBean(BlahService blahBean) {
       return new FooService(blahBean);
   }
}

Lazy loading all the dependencies via application properties

Sometimes we may wish to set all the beans to be loaded lazily. This means we may end up adding Lazy annotation on each and every bean in the application, which is not a nice thing to do.

So we can instead use the following application properties configuration to set all the beans in the application to load lazily.

# application.properties

spring.main.lazy-initialization=true

# application.yml

spring:
  main:
    lazy-initialization: true

Conclusion

Building enterprise grade applications can involve creating many beans up for injection, and sometimes many such dependencies may come with heavy and long initialization times. Even after taking such a long initialization, not all dependencies may come for use always. Lazy loading dependencies help applications start faster and helps avoid unnecessary eager initialization of dependencies which may not be used at all.

However, it’s not always a good thing to use Lazy loading everywhere. Sometimes we may end up in situations where an application needs a dependency that needs to be initialized on-demand and something may happen that results in initialization errors for that dependency. 

You can go for Lazy initialization when –

  • Your application has many beans and you want to improve startup performance.
  • You want to reduce the memory footprint by loading only required beans.

But you may want to avoid it when –

  • Your application may depend on services that need eager initialization, such as listeners or scheduled tasks.
Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.