Developer Playground
RequestContextHolder in Spring Boot: Access and Applications
Table of Contents
What is RequestContextHolder?
RequestContextHolder is a powerful utility class in Spring Framework that provides access to the current HTTP request and response objects through a thread-local storage mechanism:
- Part of Spring Web's core infrastructure (
org.springframework.web.context.request
) - Offers static methods for retrieving the current ServletRequestAttributes
- Provides access to HttpServletRequest, HttpServletResponse, and HttpSession objects
- Works automatically in Spring's request processing chain
- Enables accessing request data from anywhere in your application
How It Works
RequestContextHolder leverages Java's ThreadLocal mechanism to store request context information:
- When a request enters Spring's
DispatcherServlet
, it binds the request to the current thread - The request is wrapped in a
ServletRequestAttributes
object and stored in ThreadLocal storage - Any code executing in the same thread can access this context information
- When the request is complete, Spring cleans up the ThreadLocal to prevent memory leaks
- Uses
inheritable ThreadLocal
for potential propagation to child threads
Accessing Current Request
Spring offers two primary methods for retrieving the current request context:
Method 1: getCurrentRequest() (Non-null)
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
public HttpServletRequest getCurrentRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes)
RequestContextHolder.currentRequestAttributes();
return attributes.getRequest();
}
This approach throws an IllegalStateException
if called outside of a request context.
Method 2: getRequestOptional() (Null-safe)
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
public Optional<HttpServletRequest> getRequestOptional() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getRequest);
}
This approach safely returns an Optional, making it suitable for contexts where a request might not be available.
Common Use Cases
RequestContextHolder enables numerous practical applications across Spring applications:
1. Accessing Request Headers & Parameters
Retrieve request metadata from anywhere in your application without parameter passing:
public String getClientIP() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes()).getRequest();
String clientIP = request.getHeader("X-Forwarded-For");
if (clientIP == null || clientIP.isEmpty()) {
clientIP = request.getRemoteAddr();
}
return clientIP;
}
2. User Context & Authentication Information
Access authentication details or user context in service layers:
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
public String getCurrentUsername() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
return authentication.getName();
}
return "anonymous";
}
3. Locale & Internationalization
Access user's locale for internationalization purposes:
import org.springframework.context.i18n.LocaleContextHolder;
import java.util.Locale;
public String getLocalizedMessage(String messageKey) {
Locale userLocale = LocaleContextHolder.getLocale();
return messageSource.getMessage(messageKey, null, userLocale);
}
4. Request-Scoped Auditing
Add audit information to operations performed within a request:
@Component
public class AuditingService {
public void logOperation(String operation, String entityType, String entityId) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes()).getRequest();
String username = getCurrentUsername();
String clientIP = request.getRemoteAddr();
String userAgent = request.getHeader("User-Agent");
String requestId = request.getHeader("X-Request-ID");
// Log or store audit record
auditRepository.save(new AuditEntry(username, operation, entityType,
entityId, clientIP, userAgent, requestId, Instant.now()));
}
}
5. Request URL Building
Construct absolute URLs based on the current request:
public String buildAbsoluteUrl(String relativePath) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes()).getRequest();
String scheme = request.getScheme();
String serverName = request.getServerName();
int serverPort = request.getServerPort();
String contextPath = request.getContextPath();
// Build URL, handling standard ports (80/443)
StringBuilder url = new StringBuilder();
url.append(scheme).append("://").append(serverName);
if ((scheme.equals("http") && serverPort != 80) ||
(scheme.equals("https") && serverPort != 443)) {
url.append(":").append(serverPort);
}
if (contextPath != null && !contextPath.isEmpty()) {
url.append(contextPath);
}
if (!relativePath.startsWith("/")) {
url.append("/");
}
url.append(relativePath);
return url.toString();
}
Additional Use Cases
Beyond the common applications, RequestContextHolder enables several advanced scenarios:
- Tenant Identification - Extract and propagate tenant IDs in multi-tenant applications
- Rate Limiting - Track request rates per user or client for rate limiting
- Request Correlation - Correlate logs, metrics, and traces across system boundaries
- Feature Toggles - Apply feature flags based on request attributes
- Contextual Caching - Adapt cache behavior based on request properties
Multi-Tenant Context Using RequestContextHolder
@Component
public class TenantContext {
private static final String TENANT_HEADER = "X-Tenant-ID";
private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();
public String getCurrentTenant() {
if (CURRENT_TENANT.get() == null) {
// Try to extract from request
extractFromRequest();
}
return CURRENT_TENANT.get();
}
public void setCurrentTenant(String tenantId) {
CURRENT_TENANT.set(tenantId);
}
public void clear() {
CURRENT_TENANT.remove();
}
private void extractFromRequest() {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
String tenantId = request.getHeader(TENANT_HEADER);
if (tenantId != null && !tenantId.isEmpty()) {
CURRENT_TENANT.set(tenantId);
}
}
} catch (Exception e) {
// Safely handle case where no request is available
}
}
}
Creating a Filter for Automatic Processing
Set up a filter to automatically handle trace IDs for all requests:
@Component
public class TraceIdFilter implements Filter {
private static final String TRACE_ID_HEADER = "X-Trace-ID";
@Autowired
private TraceIdHolder traceIdHolder;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Try to extract trace ID from incoming request
String traceId = httpRequest.getHeader(TRACE_ID_HEADER);
// If no trace ID exists, generate a new one
if (traceId == null || traceId.isEmpty()) {
traceId = traceIdHolder.generateTraceId();
} else {
traceIdHolder.extractAndSetTraceId(httpRequest);
}
// Add the trace ID to the response headers for debugging
httpResponse.setHeader(TRACE_ID_HEADER, traceId);
try {
// Continue the filter chain with trace ID in context
chain.doFilter(request, response);
} finally {
// Clean up thread local to prevent memory leaks
traceIdHolder.clear();
}
}
}
ThreadLocal Memory Leaks
ThreadLocal variables can cause memory leaks if not properly managed:
@Component
public class ThreadLocalCleanupFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
chain.doFilter(request, response);
} finally {
// Clear all ThreadLocal resources
RequestContextHolder.resetRequestAttributes();
SecurityContextHolder.clearContext();
MDC.clear();
// Clear any custom ThreadLocal variables
CustomThreadLocal.clear();
}
}
}
Spring Security Integration
When using RequestContextHolder with Spring Security, consider these patterns:
@Component
public class SecurityContextAwareService {
public UserDetails getCurrentUser() {
try {
// Get authentication from SecurityContextHolder
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
return (UserDetails) authentication.getPrincipal();
}
} catch (Exception e) {
// Handle security context not available
log.warn("Security context not available", e);
}
return null;
}
public String getCurrentUsername() {
UserDetails user = getCurrentUser();
return user != null ? user.getUsername() : "anonymous";
}
}
Async Processing with CompletableFuture
Handle context propagation in async operations:
@Service
public class AsyncService {
@Async
public CompletableFuture<String> processAsync() {
// Capture current context
RequestAttributes context = RequestContextHolder.currentRequestAttributes();
SecurityContext securityContext = SecurityContextHolder.getContext();
return CompletableFuture.supplyAsync(() -> {
try {
// Set context in async thread
RequestContextHolder.setRequestAttributes(context);
SecurityContextHolder.setContext(securityContext);
// Perform async operation
return doAsyncWork();
} finally {
// Clean up context
RequestContextHolder.resetRequestAttributes();
SecurityContextHolder.clearContext();
}
});
}
}
Async Configuration with Context Propagation
Configure async execution to properly handle context propagation:
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("Async-");
executor.setTaskDecorator(new ContextCopyingDecorator());
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
public class ContextCopyingDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
RequestAttributes context = RequestContextHolder.currentRequestAttributes();
SecurityContext securityContext = SecurityContextHolder.getContext();
Map<String, String> mdcContext = MDC.getCopyOfContextMap();
return () -> {
try {
RequestContextHolder.setRequestAttributes(context);
SecurityContextHolder.setContext(securityContext);
if (mdcContext != null) {
MDC.setContextMap(mdcContext);
}
runnable.run();
} finally {
RequestContextHolder.resetRequestAttributes();
SecurityContextHolder.clearContext();
MDC.clear();
}
};
}
}
Handling Nested Async Operations
Manage context propagation in nested async operations:
@Service
public class NestedAsyncService {
@Autowired
private AsyncService asyncService;
@Async
public CompletableFuture<List<String>> processNestedAsync() {
// Capture context at the top level
RequestAttributes context = RequestContextHolder.currentRequestAttributes();
SecurityContext securityContext = SecurityContextHolder.getContext();
return CompletableFuture.supplyAsync(() -> {
try {
// Set context for the first level
RequestContextHolder.setRequestAttributes(context);
SecurityContextHolder.setContext(securityContext);
// Create multiple async tasks
List<CompletableFuture<String>> futures = new ArrayList<>();
for (int i = 0; i < 3; i++) {
futures.add(asyncService.processAsync());
}
// Wait for all tasks to complete
return futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
} finally {
// Clean up context
RequestContextHolder.resetRequestAttributes();
SecurityContextHolder.clearContext();
}
});
}
}
Async Exception Handling
Handle exceptions in async operations while maintaining context:
@Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(AsyncExceptionHandler.class);
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
try {
// Try to get request context for logging
RequestAttributes context = RequestContextHolder.getRequestAttributes();
if (context instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes) context).getRequest();
logger.error("Async error in {} for request {}: {}",
method.getName(),
request.getRequestURI(),
ex.getMessage(),
ex);
} else {
logger.error("Async error in {}: {}",
method.getName(),
ex.getMessage(),
ex);
}
} catch (Exception e) {
logger.error("Error handling async exception: {}", e.getMessage(), e);
}
}
}
Conclusion
RequestContextHolder is a powerful mechanism in Spring Boot applications that enables access to HTTP request information from anywhere in your application. By leveraging this tool effectively, you can:
- Reduce method parameter bloat by accessing request data from service layers
- Implement cross-cutting concerns like distributed tracing and logging
- Support multi-tenant applications and contextual processing
- Build more maintainable and modular applications
When using RequestContextHolder, always be mindful of thread boundaries, proper cleanup, and appropriate abstraction to avoid common pitfalls. With careful implementation, it's a valuable tool in any Spring developer's toolkit.
Advertisement