Search code examples
javaspringtomcatthread-local

Is it safe to use ThreadLocal in Spring Boot with embeded Tomcat for holding data per request


I'm using Spring Boot 2.7.0 with embeded Tomcat. And for holding user context for every request i'm using following approach:

  1. I have UserContext POJO
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserContext {
    private Long userId = null;
    private String userEmail = "";
}
  1. I have UserContextHolder class for holding UserContext in a ThreadLocal instance
import org.springframework.util.Assert;

public class UserContextHolder {
    private static final ThreadLocal<UserContext> userContext = new ThreadLocal<>();

    public static UserContext getContext() {
        UserContext context = userContext.get();

        if (context == null) {
            context = createEmptyContext();
            userContext.set(context);

        }
        return userContext.get();
    }

    public static void setContext(UserContext context) {
        Assert.notNull(context, "Only non-null UserContext instances are permitted");
        userContext.set(context);
    }

    public static UserContext createEmptyContext(){
        return new UserContext();
    }
}
  1. And I have UserContextFilter which sets data from request headers to UserContext in current thread:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
public class UserContextFilter implements Filter {
    @Value("${user-id-header-name}")
    private String userIdHeaderName;
    @Value("${user-email-header-name}")
    private String userEmailHeaderName;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;

        String userIdFromHeaders = httpServletRequest.getHeader(userIdHeaderName);
        String userEmailFromHeaders = httpServletRequest.getHeader(userEmailHeaderName);
        UserContextHolder.getContext().setUserId(userIdFromHeaders == null ? null : Long.valueOf(userIdFromHeaders));
        UserContextHolder.getContext().setUserEmail(userEmailFromHeaders);

        filterChain.doFilter(httpServletRequest, servletResponse);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void destroy() {}

}

And the question is: can I use this approach to be sure that every request will be mapped with its own thread and only one request will be processed at the same time in particular thread? I'm afraid that I can get situation when one thread will be used to process 2 or more requests (but UserContext will be the same).

And can this approach lead to memory problems?

Thank you very much for your answers :)


Solution

  • and only one request will be processed at the same time in particular thread?

    At the same time — yes, the execution inside the same thread is sequential, so it won't process multiple requests at the same time. However, it will be reused for future requests of different users, since request processing threads are pooled and reused. You don't seem to clean up the context from the thread after request processing, so this would be the problem.

    A good place to do the cleanup would be after calling filterChain.doFilter().