I need to create a code in Spring Boot for intercepting http header params. I have the following requirement for Java class body:
import java.util.HashMap;
import java.util.Map;
public final class InternalHeadersContext {
public static final String X_INTERNAL_CORRELATION_ID_HEADER = "X-INTERNAL-CORRELATION-ID";
public static final String X_INTERNAL_REQUEST_ID_HEADER = "X-INTERNAL-REQUEST-ID";
public static final String X_INTERNAL_USER_ID_HEADER = "X-INTERNAL-USER-ID";
public static void clear() {
.....
}
public static void initContext() {
......
}
public static void setInternalHeaders(Map<String, String> internalHeaders) {
.....
}
public static Map<String, String> getInternalHeaders() {
......
}
}
....
String value = (String) InternalHeadersContext.getInternalHeaders().getOrDefault("X-INTERNAL-USER-ID", "test_value");
I implemented this:
public final class InternalHeadersContext {
public static final String X_INTERNAL_CORRELATION_ID_HEADER = "X-INTERNAL-CORRELATION-ID";
public static final String X_INTERNAL_REQUEST_ID_HEADER = "X-INTERNAL-REQUEST-ID";
public static final String X_INTERNAL_USER_ID_HEADER = "X-INTERNAL-USER-ID";
// Initialize ThreadLocal with a map containing all keys (values default to null)
private static final ThreadLocal<Map<String, String>> context = ThreadLocal.withInitial(() -> {
Map<String, String> initialMap = new HashMap<>();
initialMap.put(X_INTERNAL_CORRELATION_ID_HEADER, null);
initialMap.put(X_INTERNAL_REQUEST_ID_HEADER, null);
initialMap.put(X_INTERNAL_USER_ID_HEADER, null);
return initialMap;
});
public static void clear() {
context.remove();
}
public static void initContext() {
// No-op: ThreadLocal already initializes with all keys
}
public static void setInternalHeaders(Map<String, String> internalHeaders) {
if (internalHeaders == null) {
clear();
} else {
// Create a new map with all required keys, merging with incoming headers
Map<String, String> newMap = new HashMap<>();
newMap.put(X_INTERNAL_CORRELATION_ID_HEADER, internalHeaders.getOrDefault(X_INTERNAL_CORRELATION_ID_HEADER, null));
newMap.put(X_INTERNAL_REQUEST_ID_HEADER, internalHeaders.getOrDefault(X_INTERNAL_REQUEST_ID_HEADER, null));
newMap.put(X_INTERNAL_USER_ID_HEADER, internalHeaders.getOrDefault(X_INTERNAL_USER_ID_HEADER, null));
context.set(newMap);
}
}
public static Map<String, String> getInternalHeaders() {
return context.get();
}
}
.....
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
public class InternalHeadersFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// Extract headers from the request
Map<String, String> headers = new HashMap<>();
headers.put(InternalHeadersContext.X_INTERNAL_CORRELATION_ID_HEADER,
httpRequest.getHeader(InternalHeadersContext.X_INTERNAL_CORRELATION_ID_HEADER));
headers.put(InternalHeadersContext.X_INTERNAL_REQUEST_ID_HEADER,
httpRequest.getHeader(InternalHeadersContext.X_INTERNAL_REQUEST_ID_HEADER));
headers.put(InternalHeadersContext.X_INTERNAL_USER_ID_HEADER,
httpRequest.getHeader(InternalHeadersContext.X_INTERNAL_USER_ID_HEADER));
// Update the context with headers
InternalHeadersContext.setInternalHeaders(headers);
try {
chain.doFilter(request, response);
} finally {
InternalHeadersContext.clear(); // Cleanup to avoid memory leaks
}
}
}
Full code: https://github.com/rcbandit111/error_handler_poc
I have implemented the interceptor code into a separate jar and I use it into parent proejct.
When I run the code I can't get the http value. I get NPE. What is the proper way to get http params from request?
Your cause of the NPE is here:
@Component
@ConditionalOnClass({InternalHeadersContext.class})
public class SpringSecurityAuditorAware implements AuditorAware<String>
{
public SpringSecurityAuditorAware() {
}
public Optional<String> getCurrentAuditor() {
String userName = (String) InternalHeadersContext.getInternalHeaders().getOrDefault("X-INTERNAL-USER-ID", "test");
return Optional.of(userName); // it throws NPE, because username is always null
}
}
Solution:
InternalHeadersFilter
to your configuration so that the filter is applied in the parent context. You can do it like this:@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({DatabaseConfiguration.class, InternalHeadersFilter.class}) // Filter added
public @interface EnableTracing {
}
This will solve the issue with the NPE, but only if the X-INTERNAL-USER-ID
header is present in the request.
InternalHeadersContext
, like this: // Initialize ThreadLocal with a map containing all keys (values default to null)
private static final ThreadLocal<Map<String, String>> context = ThreadLocal.withInitial(() -> {
Map<String, String> initialMap = new HashMap<>();
initialMap.put(X_INTERNAL_CORRELATION_ID_HEADER, null);
initialMap.put(X_INTERNAL_REQUEST_ID_HEADER, null);
initialMap.put(X_INTERNAL_USER_ID_HEADER, null);
return initialMap;
});
or here:
public static void setInternalHeaders(Map<String, String> internalHeaders) {
if (internalHeaders == null) {
clear();
} else {
// Create a new map with all required keys, merging with incoming headers
Map<String, String> newMap = new HashMap<>();
newMap.put(X_INTERNAL_CORRELATION_ID_HEADER, internalHeaders.getOrDefault(X_INTERNAL_CORRELATION_ID_HEADER, null));
newMap.put(X_INTERNAL_REQUEST_ID_HEADER, internalHeaders.getOrDefault(X_INTERNAL_REQUEST_ID_HEADER, null));
newMap.put(X_INTERNAL_USER_ID_HEADER, internalHeaders.getOrDefault(X_INTERNAL_USER_ID_HEADER, null)); // null can be set as value
context.set(newMap);
}
}
This causes a problem in SpringSecurityAuditorAware
. Even if the header is not sent, the map still contains the key X-INTERNAL-USER-ID
with a null
value.
So when you call .getOrDefault("X-INTERNAL-USER-ID", "test")
, it doesn't fall back to "test" — because the key is present, even though the value is null
.
To fix this, either:
or
public Optional<String> getCurrentAuditor() {
String userFromContext = InternalHeadersContext.getInternalHeaders().get("X-INTERNAL-USER-ID");
String userName = userFromContext != null
? userFromContext
: "test";
return Optional.of(userName);
}
Some refactor suggestions:
InternalHeadersContext:
import java.util.HashMap;
import java.util.Map;
public final class InternalHeadersContext {
// Initialize ThreadLocal with a new HashMap, no need to put null values there
private static final ThreadLocal<Map<String, String>> context = ThreadLocal.withInitial(HashMap::new);
public static void setInternalHeaders(Map<String, String> internalHeaders) {
context.get().putAll(internalHeaders);
}
public static Map<String, String> getInternalHeaders() {
return context.get();
}
public static void clear() {
context.remove();
}
}
InternalHeadersFilter:
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
public class InternalHeadersFilter extends OncePerRequestFilter { // changed from Filter
private static final String X_INTERNAL_CORRELATION_ID_HEADER = "X-INTERNAL-CORRELATION-ID";
private static final String X_INTERNAL_REQUEST_ID_HEADER = "X-INTERNAL-REQUEST-ID";
private static final String X_INTERNAL_USER_ID_HEADER = "X-INTERNAL-USER-ID";
@Override
public void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// Extract headers from the request
Map<String, String> map = new HashMap<>();
putIfNotNull(X_INTERNAL_CORRELATION_ID_HEADER, request, map); // put into map ONLY if not null to be able to use getOrDefault
putIfNotNull(X_INTERNAL_REQUEST_ID_HEADER, request, map);
putIfNotNull(X_INTERNAL_USER_ID_HEADER, request, map);
// Update the context with headers
InternalHeadersContext.setInternalHeaders(map);
try {
filterChain.doFilter(request, response);
} finally {
InternalHeadersContext.clear(); // Cleanup to avoid memory leaks
}
}
private void putIfNotNull(String headerName, HttpServletRequest request, Map<String, String> map) {
String headerValue = request.getHeader(headerName);
if (headerValue != null) {
map.put(headerName, headerValue);
}
}
}