Spring Bean Scopes

Spring Bean Scopes

1 Comment

When you start a Spring application, the Spring Framework creates beans for you. These Spring beans can be application beans that you have defined or beans that are part of the framework. When the Spring Framework creates a bean, it associates a scope with the bean. A scope defines the runtime context within which the bean instance is available. In Spring, a bean can be associated with the following scopes:

  • Singleton
  • Prototype
  • Request
  • Session
  • Global session
  • Application

Note: Out of the preceding scopes, Request, Session, and Application are for beans in Web-aware applications. Global session beans are for portlets.

In this post, I will discuss the different bean scopes with use cases to illustrate when to use which scope.

The Singleton Bean Scope

When you create a bean with the Singleton scope, the Spring Framework will create an instance of the bean only once. The Framework returns that instance each time the bean is requested by your application code.

I will explain through a bare minimal Spring application that models online blogs and authors.

The code of the Blog class is this.

Blog.java

package guru.springframework.beanscope.domain;

import org.springframework.beans.factory.annotation.Autowired;

public class Blog {
    private int id;
    private String title;
    public Blog() {
    }

    public Blog(int id, String title) {
        this.id = id;
        this.title = title;
    }

    /*Getter and setter for fields*/
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

As you can see, there is nothing special about the Blog class. We have a POJO with two fields – id and title with corresponding getter and setter methods. The class also has a default constructor and an overloaded one to initialize a Blog object.

Next, I will start by creating a Blog bean with the Singleton scope.

The code for the configuration class is this.

SingleDemo.java

package guru.springframework.beanscope.config;

import guru.springframework.beanscope.domain.Blog;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class SingletonDemo {
    @Bean(name = "blog")
    @Scope("singleton")
    public Blog getBlog() {
        return new Blog();
    }
}

This configuration class has a getBlog() method that returns a Blog bean. This method, in addition to the @Bean annotation, is annotated with @Scope("singleton") to set the scope of the bean.

Note: Spring Beans by default, are Singleton. This means, if you do not explicitly define a scope, the Spring Framework will create the bean with the Singleton scope. Therefore, the @Scope annotation in the preceding example is redundant.

Now that we have written the code to create a Singleton Blog bean, let’s test the code.

I am using JUnit with Spring Boot Test to unit test the code.

The test code is this.

SingletonDemoTest.java

@RunWith(SpringRunner.class)
@SpringBootTest
class SingletonDemoTest {
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    void getBlog() {
        Blog blogInstance1 = applicationContext.getBean("blog1", Blog.class);
        Blog blogInstance2 = applicationContext.getBean("blog1", Blog.class);
        assertThat(blogInstance1.equals(to(blogInstance2)));
    }
}

In the preceding test, I first autowired in the application context. In the test case, I used the application context to lookup the Blog bean and obtained two instances of the bean. I then performed an AssertJ to assert that both the instances are equal.

When I run the test, the test passes as expected because we defined Blog as a Singleton bean. Therefore the Spring Framework returns the same bean instance for both the lookups made to Blog.

The Prototype Bean Scope

When you create a bean with the Prototype scope, the Spring Framework will create a bean instance each time the bean is requested by your application code.

Going with the same example, I’ll write a configuration that creates a Blog bean with the prototype scope.

The code of the configuration class is this.

PrototypeDemo.java

package guru.springframework.beanscope.config;

import guru.springframework.beanscope.domain.Blog;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class PrototypeDemo {
    @Bean(name = "blog2")
    @Scope("prototype")
    public Blog getBlog() {
        return new Blog();
    }
}

The test code is this.

PrototypeDemoTest.java

package guru.springframework.beanscope.config;

import guru.springframework.beanscope.domain.Blog;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.internal.bytebuddy.implementation.MethodDelegation.to;
import static org.junit.jupiter.api.Assertions.*;

@RunWith(SpringRunner.class)
@SpringBootTest
class PrototypeDemoTest {
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    void getBlog() {
        Blog blogInstance1 = applicationContext.getBean("blog2", Blog.class);
        Blog blogInstance2 = applicationContext.getBean("blog2", Blog.class);
        assertThat(blogInstance1).isNotEqualTo(blogInstance2);
    }
}

The preceding test performs two lookups of the Blog bean with the name blog2. Because we declared the scope of the blog2 bean as Prototype, Spring returns a new instance of Blog for each lookup. As a result, isNotEqualTo assertion passes.

Singleton with Injected Prototype Bean Scope

In Enterprise Spring applications, you will typically have beans injected with other beans. So, it is important to understand what happens when a bean is injected with a bean with different scope.

For example, what happens when a Singleton bean is injected with a Prototype bean?

Let us find out with an example. I’ll extend the existing Blog example to introduce an Author domain object with the relationship, blog has-a author.

This is the Author POJO.

Author.java

package guru.springframework.beanscope.domain;

public class Author {
    private int id;
    private String name;

    public Author() {
    }

    public Author(int id, String name) {
        this.id = id;
        this.name = name;
    }
    /*Getter and setter methods*/

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

The refactored Blog POJO is this.

Blog.java

package guru.springframework.beanscope.config;

import guru.springframework.beanscope.domain.Author;
import guru.springframework.beanscope.domain.Blog;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class SingletonInjectedPrototypeDemo {
    @Bean(name = "blog3")
    @Scope("singleton")
    public Blog getBlog() {
        return new Blog();
    }

    @Bean(name = "author1")
    @Scope("prototype")
    public Author getAuthor() {
        return new Author();
    }
}

The next step is to write a configuration.

The code of the configuration class is this.

SingletonInjectedPrototypeDemo.java

package guru.springframework.beanscope.config;

import guru.springframework.beanscope.domain.Author;
import guru.springframework.beanscope.domain.Blog;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class SingletonInjectedPrototypeDemo {
    @Bean(name = "blog3")
    @Scope("singleton")
    public Blog getBlog() {
        return new Blog();
    }

    @Bean(name = "author1")
    @Scope("prototype")
    public Author getAuthor() {
        return new Author();
    }

}

This configuration creates a Singleton Blog and Prototype Author beans.

The test class for the preceding configuration is this.

SingletonInjectedPrototypeDemoTest .java

package guru.springframework.beanscope.config;

import guru.springframework.beanscope.domain.Author;
import guru.springframework.beanscope.domain.Blog;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.internal.bytebuddy.implementation.MethodDelegation.to;
import static org.junit.jupiter.api.Assertions.*;

@RunWith(SpringRunner.class)
@SpringBootTest
class SingletonInjectedPrototypeDemoTest {
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    void getBlog() {
        Blog blogInstance1 = applicationContext.getBean("blog3", Blog.class);
        Blog blogInstance2 = applicationContext.getBean("blog3", Blog.class);
        assertThat(blogInstance1.equals(to(blogInstance2)));

        Author authorInstance1 = blogInstance1.getAuthor();
        Author authorInstance2 = blogInstance2.getAuthor();
        assertThat(authorInstance1.equals(to(authorInstance2)));

    }
}

On execution, this test case passes.

You might be expecting the second assertion to fail. Because Author being a prototype, Spring should have created two different bean instances.

So why does the equality assertion passes?

This is known as the scoped bean injection problem. In Spring, dependencies are resolved at instantiation time. This means when you autowire the Author prototype-scoped bean into the Blog singleton-scoped bean, Spring will:

  1. Instantiate a brand new Author prototype bean.
  2. Inject the Author instance into the singleton Blog bean. That exact same Author instance will be the sole instance that is ever supplied to the Blog bean

The Request Bean Scope

The request scope is applicable to beans of Web aware applications. This scope defines a single bean definition that lives within a single HTTP request. This means every HTTP request will have its own instance of a bean.

I’ll demonstrate how the Request scope works through a REST controller.

The code of the REST controller is this.

RequestScopeController.java

package guru.springframework.beanscope.controllers;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.annotation.RequestScope;

@RestController
@RequestScope
public class RequestScopeController {

    @GetMapping("message")
    public String getMessage() {
        System.out.println(this);
        return "Hello from RequestScopeController";
    }
}

The preceding code creates a REST controller. The @RequestScope annotation sets the scope of the controller to Request. The getMessage() handler method print out the current controller instance and returns a String as the response.

Note: The @RequestScope annotation is equivalent to @Scope("singleton")

Run the application. Then open a browser to make two requests with this URL:

http://localhost:8080/message

The output in the IntelliJ Console is this.

output of running the application on the IntelliJ console
Output of request scope controller

In the preceding output, note that two different controller instances served the two requests you made.

In real-world programming, you won’t create controllers with request scope. Controllers should always be Singleton. It was only for the purpose of demonstration, I showed one. A typical use case to create a bean in request scope is for information that should only be valid on one page. For example, the confirmation of an order. The bean will be valid until the page is reloaded.

The Session Bean Scope

The session scope defines a single bean definition which lives within the lifecycle of an HTTP Session. Similar to the Request scope, the Session scope is applicable to beans in Web applications.

The code for a REST controller with session scope is this.

package guru.springframework.beanscope.controllers;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.annotation.RequestScope;
import org.springframework.web.context.annotation.SessionScope;
import org.w3c.dom.ls.LSOutput;

@RestController
@SessionScope
public class SessionScopeController {

    @GetMapping("/session/message")
    public String getMessage() {
        System.out.println(this);
        return "Hello from SessionScopeController";
    }
}

Run the application and access the controller multiple times from a browser window using this URL.
http://localhost:8080/session/message
The application output is this.
output of running the application on the IntelliJ console

Note the output. As the requests are coming from the same session, the same controller instance is serving the requests.

Open a browser window in incognito mode and access the same URL a few times.
The application output now is this.
output of running the application on the IntelliJ console

As you can see, because the requests are now being sent from a different session, a new controller instance serves the current set of requests.

Other Scopes

There are two other lesser used scopes: Global-Session and Application scopes
Global session scope defines a single bean definition to the lifecycle of a global HTTP Session. This scope is valid when used in a portlet context.

When your application is built of portlets, they run in Portlet container. Each portlet has its own session, but if you want to store variables global for all portlets in your application than you should scope them in Global Session.

In the application scope, Spring creates a bean instance per web application runtime. It is similar to singleton scope, with one major difference. Singleton scoped bean is singleton per ApplicationContext where application scoped bean is singleton per ServletContext. Please note that there can be multiple application contexts for a single application.

Summary

When you develop Enterprise Applications using the Spring Framework, selecting the right scope for your beans is crucial. Most of the time, the vast majority of business logic we create can be safely kept in stateless objects. And the best choice for stateless beans is the singleton scope. This is the reason for Singleton being the default scope.

The prototype scope is better for stateful beans to avoid multithreading issues.

A session-scoped bean is useful to hold authentication information getting invalidated when the session is closed (by timeout or logout). You can store other user information that you don’t want to reload with every request here as well.

The source code for this post can be found here on GitHub.

About SFG Contributor

Staff writer account for Spring Framework Guru

    You May Also Like