Spring Request Context Implementation Options | Generated by AI

Home 2025.09

Great use-case for a tiny “request context” in Spring. You’ve got a few clean options—pick one based on your stack:

1) Request-scoped bean (simple & safe)

Store the test user id in a request-scoped bean that your TestUserFilter sets, and any component can inject/read.

// holds the value for the lifetime of a single HTTP request
@Component
@RequestScope
public class TestUserContext {
  private String userId;
  public String getUserId() { return userId; }
  public void setUserId(String userId) { this.userId = userId; }
}
@Component
public class TestUserFilter extends OncePerRequestFilter {
  private final TestUserContext ctx;
  public TestUserFilter(TestUserContext ctx) { this.ctx = ctx; }

  @Override
  protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
      throws ServletException, IOException {
    String id = req.getHeader("X-Test-User-Id");
    if (id == null) id = req.getParameter("testUserId");
    if (id != null && !id.isBlank()) ctx.setUserId(id);
    chain.doFilter(req, res); // request-scope bean is auto-cleaned after request ends
  }
}

Use anywhere:

@Service
public class SomeService {
  private final TestUserContext ctx;
  public SomeService(TestUserContext ctx) { this.ctx = ctx; }
  public void doWork() {
    String testUserId = ctx.getUserId(); // null if not provided
  }
}

Why: No manual cleanup; works across controllers/services/DAOs within the same request.


Let the filter set an Authentication so all components can read it via SecurityContextHolder or @AuthenticationPrincipal.

@Component
public class TestUserFilter extends OncePerRequestFilter {
  @Override
  protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
      throws IOException, ServletException {
    String id = req.getHeader("X-Test-User-Id");
    if (id == null) id = req.getParameter("testUserId");

    if (id != null && !id.isBlank()) {
      var auth = new UsernamePasswordAuthenticationToken(
          id, "N/A", List.of(new SimpleGrantedAuthority("ROLE_TEST")));
      SecurityContextHolder.getContext().setAuthentication(auth);
    }
    try {
      chain.doFilter(req, res);
    } finally {
      SecurityContextHolder.clearContext();
    }
  }
}

Use anywhere:

String testUserId = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

or inject Principal/Authentication in controllers.

Why: Plays nicely with method security, auditing, logs, etc.


3) ThreadLocal holder (works without web/security deps; remember to clear)

If you’re not using request scope or Security:

public final class TestUserHolder {
  private static final ThreadLocal<String> USER_ID = new InheritableThreadLocal<>();
  public static void set(String id) { USER_ID.set(id); }
  public static String get() { return USER_ID.get(); }
  public static void clear() { USER_ID.remove(); }
}
@Component
public class TestUserFilter extends OncePerRequestFilter {
  @Override
  protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
      throws IOException, ServletException {
    String id = req.getHeader("X-Test-User-Id");
    if (id == null) id = req.getParameter("testUserId");
    if (id != null && !id.isBlank()) TestUserHolder.set(id);
    try {
      chain.doFilter(req, res);
    } finally {
      TestUserHolder.clear(); // CRITICAL for pooled threads
    }
  }
}

If you run async tasks and need propagation, add a TaskDecorator to copy the ThreadLocal.


Quick tips


Back Donate