Bean scopes and why state is a bug

Spring Boot · lesson 18 of 39 · 4 min read

One singleton serves every request thread, so a mutable field leaks data between users.

Open this lesson in the learning hub

Key points

  • The default scope is singleton: one instance for the whole context, shared by every request thread.
  • That is safe only while the bean is stateless. A mutable field is shared mutable state under load.
  • Keep per-request data in parameters and local variables - those live on the calling thread stack.
  • prototype hands out a new instance per lookup, but a singleton that injects one still keeps the same copy.
  • For genuine per-request state use @RequestScope; Spring injects a proxy that resolves per request.
  • Never park the logged-in user in a field. Take it as an argument or read the security context.

Example

@Service
class BrokenOrderService {        // singleton: ONE instance for the whole app

    private String currentUser;   // shared by every request thread

    List<Order> forUser(String user) {
        this.currentUser = user;                       // thread B overwrites this...
        List<Order> found = repo.findByUser(this.currentUser);
        return filter(found, this.currentUser);        // ...before thread A reads it
    }
}

@Service
class GoodOrderService {          // still a singleton, but stateless

    List<Order> forUser(String user) {
        return filter(repo.findByUser(user), user);    // no field, nothing shared
    }
}

@Component
@RequestScope                     // genuinely per request, injected as a proxy
class RequestContext {
    private String traceId;       // getters and setters omitted
}

Singleton is the right default, and it only works because you keep the bean stateless.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Spring Boot course, and every lesson in it is listed on the Spring Boot contents page.