DEV Community

Sandeep Yadav
Sandeep Yadav

Posted on

How Does Request Scope Actually Work in Spring?

If you are not interested in theory, then I have uploaded video for request scope on youtube.
URL : https://youtu.be/GYyKYB9-72Q

The Spring Framework provides several bean scopes that determine the lifecycle and visibility of beans in the application context.

The Scopes are :

  1. Singleton
  2. Prototype
  3. Request
  4. Session
  5. Application
  6. WebSocket

The last four scopes (request, session, application, and WebSocket) are specifically designed for web applications.

I will explain the request scope in detail.

In Spring, a request-scoped bean is a bean that is created once per HTTP request and destroyed when the request completes.
This means when the application receives an Http request:

  1. It creates an instance of the request-scoped bean specifically for that request.
  2. The bean remains available throughout the request’s lifecycle.
  3. It gets automatically destroyed when the request processing completes.

When a bean is defined request scoped bean then each HTTP request gets its own unique instance of the bean. It is useful for storing request-specific data.

To understand the request scope bean, I am going to an example of the ticket booking system.

Image description

In the diagram, you can see the components of a ticket booking system. The following are the components:

  1. Filter (JwtTokenFilter)
  2. Rest controller (Booking controller)
  3. Two services(BookingService and Notificationservice)
  4. Model class(UserContext) The fourth compoenent is user contrext bean. That would be injected into The jwt token filter, BookingService class and notification service.

UserContext is a class that holds the current user’s information(user’s ID and name). It is declared a bean using @Component annotation and its scope is request. The UserContext bean is also injected into the BookingService and NotificationService.

Flow :
Step 1: The user sends a request to the Ticket Booking application. The filter first intercepts the request. The filter, JwtTokenFilter, validates the token, extracts the user ID and name from it, and stores them in the UserContext bean.

Steps 2 : The request is received by the rest controller.

Step 3: The rest controller uses the BookingService to book the ticket.

Step 4: The rest controller then uses the NotificationService to send confirmation notification to user about the booking status.

The scope of the UserContext bean is request, so the same UserContext bean is injected into BookingService and NotificationService classes.
The current user’s information that is set into the request scoped bean UserContext in the filter will be available in the Booking service and Notification Service.

Project structure

Image description

I have created a Spring Boot project using Spring Boot version 3.2.4. The Java version is 21 (or 22, as Java 23 is not yet fully supported). I added five dependencies: the first two are Spring-related (spring-boot-starter-web and spring-boot-starter-security), and the last three are for JWT token handling (jjwt-api, jjwt-impl, and jjwt-jackson).

This project follows a layered Spring Boot architecture with four main packages:
controller — Contains REST API endpoints (e.g., UserController, AuthController).
filter — Houses security filters (e.g., JWT validation filters like JwtAuthFilter).
model — Includes entity/DTO classes (e.g., User, LoginRequest).
service — Holds business logic (e.g., UserService, JwtService).

1. UserContext class
In the model package, I’ve created a UserContext class.It is a request-scoped Spring bean that holds user-specific information (like userID and name) during HTTP request lifecycle. It is used in JWT-based authentication where JwtTokenFilter populates user details after token validation.

`package com.learn.bean.scopes.model;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;

import java.util.UUID;

@Component
@scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserContext {

private static Logger logger = LoggerFactory.getLogger(UserContext.class);

private String id;
private String userID;
private String name;

public UserContext() {
    this.id = UUID.randomUUID().toString();
    logger.info("UserContext bean is created, Bean ID : {}", id);
}

public void setUserID(String userID) {
    this.userID = userID;
}

public String getUserID() {
    return userID;
}

public String getName() {
    return name;
}

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

@Override
public String toString() {
    return "UserContext{" +
            "Bean ID='" + id + '\'' +
            ", User ID='" + userID + '\'' +
            ", User Name='" + name + '\'' +
            '}';
}
Enter fullscreen mode Exit fullscreen mode

}`

@scope(value = WebApplicationContext.SCOPE_REQUEST): The class is also annotated with @scope annotation,it defines the lifecycle scope of a userContext bean. By setting attribute value = “request”, the bean becomes request-scoped, meaning:
A new instance is created for each HTTP request.
The instance is destroyed when the request completes.

proxyMode = ScopedProxyMode.TARGET_CLASS: The second attribute in @scope is proxyMode and its value is target classs. It ensures that Spring creates a proxy to inject the request-scoped bean (UserContext) into singleton beans (e.g., controllers/services). At runtime, the proxy fetches the actual instance from the application context per HTTP request.

2. BookingService class
In the service package I have created a BookingService class. It is a Spring service (@Service) responsible for handling ticket booking logic. The request-scoped UserContext bean is injected into this service class.

`package com.learn.bean.scopes.service;

import com.learn.bean.scopes.model.UserContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookingService {
private static Logger logger = LoggerFactory.getLogger(BookingService.class);

@Autowired
private UserContext userContext;

public String bookTicket() {
    String eventId = "Ticket Booking";
    logger.info("User context object in booking service {}",userContext);
    // ticket booking logic goes here
    logger.info("Ticket booked for event: {}  by user: {} ",eventId, userContext.getName());
    return "Ticket booked successfully for user: " + userContext.getName();
}
Enter fullscreen mode Exit fullscreen mode

}`

Business Method: bookTicket()
In the bookTicket() method, I’ve added logging to track the UserContext object (logging beanId, userId, and name) for verification purposes. The method then proceeds with the ticket booking logic. Upon successful booking, the booking details are logged, and a confirmation message is returned.

3. NotificationService class
In the service package I have created a NotificationService class. It is a Spring service (@Service) responsible for sending notifications (e.g., booking confirmations) to users. The request-scoped UserContext bean is injected in this class to send personalized notifications.

`package com.learn.bean.scopes.service;
import com.learn.bean.scopes.model.UserContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class NotificationService {

private static Logger logger = LoggerFactory.getLogger(NotificationService.class);

@Autowired
private UserContext userContext;

public String sendConfirmation() {
    String eventId = "Ticket Booking";
    logger.info("User context object in notification service {}",userContext);
    // Send notification logic goes here
    logger.info("Confirmation sent to user: {} for event: {}",userContext.getName(), eventId);
    return "Ticket is booked for user: " + userContext.getName();
}
Enter fullscreen mode Exit fullscreen mode

}`

sendConfirmationMethod(): In this method, I’ve added logging to track the UserContext object (including beanId, userId, and name) for verification. Next, we will implement the notification logic. Upon successfully sending the notification, the details are logged, and a confirmation message is returned.

4. BookingController
In the controller package I have created a BookingController class.It is a Spring REST controller (@RestController) that handles HTTP requests related to ticket booking. It coordinates between the BookingService (for ticket processing) and NotificationService (for sending confirmations).

The BookingService and NotificationService dependencies are injected in the controller.
BookingService: Contains business logic for ticket booking.
NotificationService: Handles sending confirmation messages (e.g., emails, SMS).

It returns a confirmation message to the user after successful ticket booking.

`package com.learn.bean.scopes.controller;

import com.learn.bean.scopes.service.NotificationService;
import com.learn.bean.scopes.service.BookingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BookingController {

@Autowired
private BookingService ticketBookingService;

@Autowired
private NotificationService notificationService;


@GetMapping("/bookTicket")
public ResponseEntity<String> bookTicket() {
    ticketBookingService.bookTicket();
    notificationService.sendConfirmation();
    return ResponseEntity.ok("Ticket booked successfully");
}
Enter fullscreen mode Exit fullscreen mode

}`

5. JwtTokenFilter class
In the filter package, I have created a JwtTokenFilter class. It is a Spring component (@Component)that is responsible for JWT-based authentication. It intercepts incoming HTTP requests, validates JWT tokens, and populates user information into the UserContext.

`package com.learn.bean.scopes.filter;

import com.learn.bean.scopes.model.UserContext;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Date;

@Component
public class JwtTokenFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(JwtTokenFilter.class);

@Autowired
private UserContext userContext;

@Value("${secret-key}")
private String secretKey;

public JwtTokenFilter() {
    logger.info("JwtTokenFilter bean is created");
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    try {
        String header = request.getHeader("Authorization");
        String token = (header != null && header.startsWith("Bearer ")) ? header.substring(7) : null;
        if (token != null) {
            Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
            validateToken(claims);
            userContext.setUserID(claims.getSubject());
            userContext.setName((String) claims.get("name"));
            logger.info("userContext object is populated by JWtTokenFilter");
            logger.info("User context object in jwt token filter {}",userContext);
        }
        chain.doFilter(request, response);
    } catch (JwtException e) {
        logger.warn("JWT validation failed: {}", e.getMessage());
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token");
    }
}

private void validateToken(Claims claims) throws JwtException {
    if (claims.getExpiration().before(new Date())) {
        throw new JwtException("Token expired");
    }
    // userID validation code goes here
    // extract the userID and check the userID in DB, if userID is present,
    // it's a valid request, otherwise thrown Invalid user exception
}
Enter fullscreen mode Exit fullscreen mode

}`

Image description

It gets the Authorization header from the request. If header is valid, it extracts the token by removing “Bearer “. If toke is present then it extracts the claims (payload) from the token using secret key. It validates the token by checking the expiration date. If taken ias valid then it populates the user details in User Context bean.Then the request proceeds to the next filter.

If token validation fails (e.g., expired, tampered, or invalid), a 401 Unauthorized error is sent. The filter chain is not continued, blocking the request.

6. Application properties
It contains a secret key, that is used by JwtFilter to extract the attributes from JWT token.

`spring.application.name=request-scope

secret-key=gDBp6ZZOuL/L4pJ0tkPjbJQfcgOyIt8g60e3UyciK78=`

7. JwtTokenService
To generate JWT token for testing purpose, In the service package, I’ve implemented a JwtTokenService class that generates JWT tokens.

The method generateSecretKey() generates a secret key and generateToken() method uses that secret key to generate the token.
We need to add the secret key in the application.properties file. This secret key will be used by JwtToken filter to extract the claims from the token.

`package com.learn.bean.scopes.service;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtTokenService {

public static void main(String[] args) {
    long duration = 1000 * 60 * 30; // 30 minutes in milliseconds
    String token = generateToken(duration, generateSecretKey());
    System.out.println("Token: " + token);
}

public static String generateToken(long duration, Key secretKey) {
    Map<String, Object> claims = new HashMap<>();
    claims.put("name", "Java Learnerss");  // Name
    claims.put("role", "USER");   // Custom claim

    return Jwts.builder()
            .setClaims(claims)
            .setSubject("javalearnerss")
            .setIssuedAt((new Date(System.currentTimeMillis())))
            .setExpiration(new Date(System.currentTimeMillis() + duration))
            .signWith(secretKey)
            .setHeaderParam("typ", "JWT")
            .compact();
}

public static Key generateSecretKey() {
    Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
    // Display the security key
    byte[] keyBytes = secretKey.getEncoded();
    String base64Key = Encoders.BASE64.encode(keyBytes);
    System.out.println("Generated key: " + base64Key);
    return secretKey;
}
Enter fullscreen mode Exit fullscreen mode

}`

Generate the secret key and token.

Image description

Copy this secret key and paste it into the application.properties file.

Run the application

Image description

Send the request from postman.

Image description

In the below screenshot, the app logs show that same UserContext bean is injected into JwtTokenFilter, BookingService and NotificationService classes.

Image description

Top comments (0)