Partner – Microsoft – NPI (cat= Spring)
announcement - icon

Azure Spring Apps is a fully managed service from Microsoft (built in collaboration with VMware), focused on building and deploying Spring Boot applications on Azure Cloud without worrying about Kubernetes.

And, the Enterprise plan comes with some interesting features, such as commercial Spring runtime support, a 99.95% SLA and some deep discounts (up to 47%) when you are ready for production.

>> Learn more and deploy your first Spring Boot app to Azure.

You can also ask questions and leave feedback on the Azure Spring Apps GitHub page.

1. Overview

In this tutorial, we’ll learn how Spring beans created with the singleton scope work behind the scenes to serve multiple concurrent requests. Furthermore, we’ll understand how Java stores the bean instances in memory and how it handles concurrent access to them.

2. Spring Beans and Java Heap Memory

The Java heap, as we know, is a globally shared memory accessible to all the running threads within an application. When the Spring container creates a bean with the singleton scope, the bean is stored in the heap. This way, all the concurrent threads are able to point to the same bean instance.

Next, let’s understand what the stack memory of a thread is and how it helps to serve concurrent requests.

3. How Are Concurrent Requests Served?

As an example, let’s take a Spring application that has a singleton bean called ProductService:

@Service
public class ProductService {
    private final static List<Product> productRepository = asList(
      new Product(1, "Product 1", new Stock(100)),
      new Product(2, "Product 2", new Stock(50))
    );

    public Optional<Product> getProductById(int id) {
        Optional<Product> product = productRepository.stream()
          .filter(p -> p.getId() == id)
          .findFirst();
        String productName = product.map(Product::getName)
          .orElse(null);

        System.out.printf("Thread: %s; bean instance: %s; product id: %s has the name: %s%n", currentThread().getName(), this, id, productName);

        return product;
    }
}

This bean has a method getProductById() which returns product data to its callers. Further, the data returned by this bean is exposed to the clients on the endpoint /product/{id}.

Next, let’s explore what happens at runtime when simultaneous calls hit the endpoint /product/{id}. Specifically, the first thread will call the endpoint /product/1 and the second thread will call /product/2.

Spring creates a different thread for each request. As we see in the console output below, both threads use the same ProductService instance to return the product data:

Thread: pool-2-thread-1; bean instance: com.baeldung.concurrentrequest.ProductService@18333b93; product id: 1 has the name: Product 1
Thread: pool-2-thread-2; bean instance: com.baeldung.concurrentrequest.ProductService@18333b93; product id: 2 has the name: Product 2

It’s possible for Spring to use the same bean instance in multiple threads, firstly because for each thread, Java creates a private stack memory.

The stack memory is responsible for storing the states of the local variables used inside methods during thread execution. This way, Java makes sure that threads executing in parallel do not overwrite each other’s variables.

Secondly, because ProductService bean sets no restrictions or locks at the heap level, the program counter of each thread is able to point to the same reference of the bean instance in the heap memory. Therefore, both threads can execute the getProdcutById() method simultaneously.

Next, we’ll understand why it’s crucial for singleton beans to be stateless.

4. Stateless Singleton Beans vs. Stateful Singleton Beans

To understand why stateless singleton beans are important, let’s see what the side effects of using stateful singleton beans are.

Suppose we moved the productName variable to the class level:

@Service
public class ProductService {
    private String productName = null;
    
    // ...

    public Optional getProductById(int id) {
        // ...

        productName = product.map(Product::getName).orElse(null);

       // ...
    }
}

Now, let’s run the service again and look at the output:

Thread: pool-2-thread-2; bean instance: com.baeldung.concurrentrequest.ProductService@7352a12e; product id: 2 has the name: Product 2
Thread: pool-2-thread-1; bean instance: com.baeldung.concurrentrequest.ProductService@7352a12e; product id: 1 has the name: Product 2

As we can see, the call for productId 1 shows the productName “Product 2” instead of “Product 1”. This happens because the ProductService is stateful and shares the same productName variable with all running threads.

To avoid undesired side effects like this, it’s crucial to keep our singleton beans stateless.

5. Conclusion

In this article, we saw how concurrent access to singleton beans works in Spring. Firstly, we looked at how Java stores singleton beans in the heap memory. Next, we learned how different threads access the same singleton instance from the heap. Finally, we discussed why having stateless beans is important and looked at an example of what can happen if the beans are not stateless.

As always, the code for these examples is available on GitHub.

Course – LS (cat=Spring)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> THE COURSE
res – REST with Spring (eBook) (everywhere)
9 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.