Backend Development 6 min read

Resolving Context Loss in Asynchronous openFeign Calls Using RequestContextHolder

This article explains why asynchronous openFeign calls trigger Sentinel fallback due to missing request context, and provides a practical solution that copies the main thread's RequestAttributes into async threads to preserve JWT tokens and other headers.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Resolving Context Loss in Asynchronous openFeign Calls Using RequestContextHolder

When using openFeign for asynchronous calls, developers often encounter failures and Sentinel fallback because the request context (e.g., JWT token) is lost in the new thread, while synchronous calls work fine.

The root cause is that Spring stores request information in RequestContextHolder , which relies on ThreadLocal . Asynchronous threads do not inherit this data, leading to missing headers during remote calls.

To fix the issue, capture the current thread's RequestAttributes before launching the async task and set it inside each async thread before invoking the Feign client.

Example of the original asynchronous call:

CompletableFuture<T> future1 = CompletableFuture.supplyAsync(() -> {
    // openfeign call
    return feign.remoteCall();
}, executor);

CompletableFuture<T> future2 = CompletableFuture.supplyAsync(() -> {
    // openfeign call
    return feign.remoteCall();
}, executor);

CompletableFuture.allOf(future1, future2).join();

Interceptor to propagate headers:

@Component
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        HttpServletRequest httpServletRequest = RequestContextUtils.getRequest();
        Map
headers = getHeaders(httpServletRequest);
        for (Map.Entry
entry : headers.entrySet()) {
            template.header(entry.getKey(), entry.getValue());
        }
    }

    /** Get original request headers */
    private Map
getHeaders(HttpServletRequest request) {
        Map
map = new LinkedHashMap<>();
        Enumeration
enumeration = request.getHeaderNames();
        if (enumeration != null) {
            while (enumeration.hasMoreElements()) {
                String key = enumeration.nextElement();
                String value = request.getHeader(key);
                if (StrUtil.equals(OAuthConstant.TOKEN_NAME, key)) {
                    map.put(key, value);
                    break;
                }
            }
        }
        return map;
    }
}

Applying the context transfer in the async tasks:

// Capture main thread request attributes
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();

CompletableFuture<T> future1 = CompletableFuture.supplyAsync(() -> {
    // Set attributes for async thread
    RequestContextHolder.setRequestAttributes(attributes);
    // openfeign call
    return feign.remoteCall();
}, executor);

CompletableFuture<T> future2 = CompletableFuture.supplyAsync(() -> {
    RequestContextHolder.setRequestAttributes(attributes);
    return feign.remoteCall();
}, executor);

CompletableFuture.allOf(future1, future2).join();

By propagating the request attributes, the JWT token and other necessary headers are retained, preventing fallback and ensuring successful asynchronous Feign calls.

JavaSpring CloudOAuth2AsyncOpenFeignRequestContextHolder
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.