Backend Development 5 min read

Understanding Spring Boot Controller Scope: Singleton vs Prototype and Thread Safety

This article explains why Spring MVC controllers are singleton by default, demonstrates the thread‑safety issues caused by non‑static member variables, shows how applying @Scope("prototype") changes behavior, and summarizes the five Spring bean scopes with practical recommendations.

Java Captain
Java Captain
Java Captain
Understanding Spring Boot Controller Scope: Singleton vs Prototype and Thread Safety

By default, a Spring MVC @Controller is a singleton, so using non‑static member variables can lead to data inconsistency because the instance is shared across threads.

Example code demonstrates the problem:

package com.riemann.springbootdemo.controller;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author riemann
 * @date 2019/07/29 22:56
 */
@Controller
public class ScopeTestController {

    private int num = 0;

    @RequestMapping("/testScope")
    public void testScope() {
        System.out.println(++num);
    }

    @RequestMapping("/testScope2")
    public void testScope2() {
        System.out.println(++num);
    }
}

When accessing http://localhost:8080/testScope the output is 1 ; a subsequent call to /testScope2 prints 2 , showing that the same controller instance is reused and the state is shared, which is not thread‑safe.

Changing the controller to prototype scope resolves the issue:

package com.riemann.springbootdemo.controller;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author riemann
 * @date 2019/07/29 22:56
 */
@Controller
@Scope("prototype")
public class ScopeTestController {

    private int num = 0;

    @RequestMapping("/testScope")
    public void testScope() {
        System.out.println(++num);
    }

    @RequestMapping("/testScope2")
    public void testScope2() {
        System.out.println(++num);
    }
}

Now both /testScope and /testScope2 return 1 on each request, confirming that a new controller instance is created each time.

Key takeaways:

Do not define mutable member variables in a singleton controller.

If a member variable is required, annotate the controller with @Scope("prototype") to make it prototype‑scoped.

Alternatively, use ThreadLocal for thread‑confined data.

Spring bean scopes overview (five types):

singleton : one instance per Spring container (eagerly created unless lazy-init is used).

prototype : a new instance each time getBean is called; Spring does not manage the lifecycle after creation.

request : a new instance for each HTTP request, managed by Spring in web applications.

session : a new instance for each HTTP session.

global session : a single instance for the entire web application, similar to the servlet application scope.

For further reading, a recommended Spring Boot tutorial is available at http://blog.didispace.com/spring-boot-learning-2x/ .

backendSpring BootThread SafetyControllersingletonprototypeScope
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

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.