1. Overview

In this tutorial, we'll look at how we can manage request-scoped data using Spring MVC.

Request-scoped data lives as long as the request is active. For example, we can store the current request body or currently authenticated user.

2. Sample Application

Let's start with our sample application.

@RestController
public class PersonController {

    @PostMapping("/person")
    public String person(@RequestBody Person person) {
        throw new RuntimeException("Planned");
    }

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<String> handleRuntimeException() {
        // Exception handling logic ...
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error occurred");
    }
}

Here, we have the PersonController classIt has one endpoint - /person - which expects Person as the payload. Also, note that we're using the @RequestBody annotation to bind the incoming data.

Moreover, for our purposes, we're always throwing a runtime exception in our endpoint. Then we're declaring the handleRuntimeException method to handle those RuntimeExceptions

Although the exception handling method doesn't have access to the Person data coming with the request, we'll use this data when handling the exception.

3. @RequestScope

We'll start with the @RequestScope annotation.

@RequestScope sets the scope of a Spring bean to the current web request.

Firstly, to store request related properties, we'll create a Spring bean and annotate it with @RequestScope:

@Component
@RequestScope
public class RequestScopedContext {

    private Person person;

    public void set(Person person) {
        this.person = person;
    }

    public Person get() {
        return person;
    }
}

Here, we have the RequestScopedContext bean which has the person field.

Secondly, we must assign the incoming Person data to the person field. Since we're using @RequestBody in our endpoint, we can use RequestBodyAdvice to hook into the Spring MVC's processing flow.

@ControllerAdvice
public class ForRequestScopeRequestBodyAdvice extends RequestBodyAdviceAdapter {

    @Autowired
    private RequestScopedContext requestScopedContext;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return targetType.getTypeName() == Person.class.getTypeName() && methodParameter.getContainingClass() == PersonController.class;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                Class<? extends HttpMessageConverter<?>> converterType) {
        if (body instanceof Person) {
            requestScopedContext.set((Person) body);
        }

        return body;
    }
}

When Spring converts the incoming data to a Person object, we'll assign it to the person field of RequestScopedContext.

Now that we've set the Person object, other beans in the application context can access this data through the RequestScopedContext bean. Furthermore, this data will be available as long as the request is active:

@Service
public class ExceptionHelperService {

    @Autowired
    private RequestScopedContext requestScopedContext;

    public Person getAndProcessPersonFromRequestScope() {
        return requestScopedContext.get();
    }
}

Here, we're accessing the Person data from exception handling service.

4. Custom ThreadLocal

Next, we'll look at the custom ThreadLocal approach.

Web servers like Tomcat or Jetty maintain a thread pool to serve the incoming requests. When the server assigns a thread to a new request, it runs on the assigned thread until the response is sent. This is the case for synchronous processing.

And since the ThreadLocal objects are bound to the current thread, we can use them to store request-scoped data:

public class ThreadLocalRequestContext {

    private static final InheritableThreadLocal<Person> REQUEST_THREAD_LOCAL = new InheritableThreadLocal<>();

    public static void set(Person person) {
        REQUEST_THREAD_LOCAL.set(person);
    }

    public static Person get() {
        return REQUEST_THREAD_LOCAL.get();
    }

    public static void clear() {
        REQUEST_THREAD_LOCAL.remove();
    }
}

Here, we have the ThreadLocalRequestContext class which has an InheritableThreadLocal<Person> field - REQUEST_THREAD_LOCAL. We also have static get, set and clear methods.

As the next step, we must initialize REQUEST_THREAD_LOCAL with the converted Person object. We'll again use a RequestBodyAdvice implementation.

@ControllerAdvice
public class ForThreadLocalRequestBodyAdvice extends RequestBodyAdviceAdapter {

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                Class<? extends HttpMessageConverter<?>> converterType) {
        if (body instanceof Person) {
            ThreadLocalRequestContext.set((Person) body);
        }

        return body;
    }

    // Other methods ...
}

Lastly, we'll access our ThreadLocal object from the exception handling class - ExceptionHelperService:

@Service
public class ExceptionHelperService {

    public Person getAndProcessFromThreadLocal() {
        return ThreadLocalRequestContext.get();
    }
}

5. Built-in ThreadLocal

Spring MVC provides the RequestContextHolder class which we can use to store request related properties. Behind the scenes, this class also uses ThreadLocal objects.

Similar to the previous examples, we'll create a RequestBodyAdvice implementation:

@ControllerAdvice
public class ForBuiltinRequestBodyAdvice extends RequestBodyAdviceAdapter {
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                Class<? extends HttpMessageConverter<?>> converterType) {
        if (body instanceof Person) {
            RequestContextHolder.currentRequestAttributes().setAttribute("person", body, RequestAttributes.SCOPE_REQUEST);
        }

        return body;
    }

    // Other methods...
}

Then, we'll access the request data through a static call from ExceptionHelperService:

@Service
public class ExceptionHelperService {

    public Person getAndProcessPersonFromRequestContextHolder() {
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        return (Person) requestAttributes.getAttribute("person", RequestAttributes.SCOPE_REQUEST);
    }
}

6. Summary

In this tutorial, we've investigated how to manage request-scoped data using Spring MVC.

Firstly, we looked at the request-scoped beans using the @RequestScope annotation. Then we investigated the ThreadLocal solutions both custom and built-in.

As always, the source code for all examples in this tutorial is available on Github.