Backend Development 12 min read

ThreadLocal Context Propagation with Hystrix

The article explains how to propagate ThreadLocal data such as a traceId across Hystrix’s thread‑pool isolation by implementing a custom HystrixConcurrencyStrategy or, more robustly, a HystrixCommandExecutionHook that uses HystrixRequestContext, ensuring the context is correctly transferred even to fallback methods.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
ThreadLocal Context Propagation with Hystrix

This article shares a solution for passing ThreadLocal context information when using Hystrix.

Background : In business development, ThreadLocal is used to store key information such as traceId. When Hystrix is applied for circuit breaking, the ThreadLocal value may not be correctly propagated to the Hystrix thread.

ThreadLocal : Explanation and example code for storing traceId.

public class ThreadLocalUtil {
    private static final ThreadLocal
TRACE_ID = new ThreadLocal<>();
    public static void setTraceId(String traceId) { TRACE_ID.set(traceId); }
    public static String getTraceId() { return TRACE_ID.get(); }
    public static void clearTraceId() { TRACE_ID.remove(); }
}

Hystrix : Overview of Hystrix circuit breaker and its two isolation strategies (semaphore and thread‑pool). The default is thread‑pool, which creates a separate thread pool for command execution.

Problem : In thread‑pool mode, ThreadLocal values are not automatically transferred to the Hystrix thread, leading to mismatched traceId.

Solution 1 – InheritableThreadLocal : Not suitable because Hystrix reuses threads from its pool, causing stale values.

Solution 2 – HystrixConcurrencyStrategy : Implement a custom concurrency strategy that copies the ThreadLocal value before the command runs and clears it afterwards.

public class MyHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
    @Override
    public
Callable
wrapCallable(Callable
callable) {
        String traceId = ThreadLocalUtil.getTraceId();
        return () -> {
            ThreadLocalUtil.setTraceId(traceId);
            try {
                return callable.call();
            } finally {
                ThreadLocalUtil.clearTraceId();
            }
        };
    }
}
// Register
HystrixPlugins.getInstance().registerConcurrencyStrategy(new MyHystrixConcurrencyStrategy());

This approach works for normal command execution but does not cover fallback methods.

Solution 3 – HystrixCommandExecutionHook : Override hook methods to copy and paste the traceId for onStart, onExecutionStart, onFallbackStart, and clean up after success or error.

public class MyHystrixHook extends HystrixCommandExecutionHook {
    private String traceId;
    @Override
    public
void onStart(HystrixInvokable
commandInstance) { copyTraceId(); }
    @Override
    public
void onExecutionStart(HystrixInvokable
commandInstance) { pasteTraceId(); }
    @Override
    public
void onFallbackStart(HystrixInvokable
commandInstance) { pasteTraceId(); }
    // option1
    @Override
    public
void onExecutionSuccess(HystrixInvokable
commandInstance) {
        ThreadLocalUtil.clearTraceId();
        super.onExecutionSuccess(commandInstance);
    }
    @Override
    public
Exception onExecutionError(HystrixInvokable
commandInstance, Exception e) {
        ThreadLocalUtil.clearTraceId();
        return super.onExecutionError(commandInstance, e);
    }
    // option2 …
    private void copyTraceId() { this.traceId = ThreadLocalUtil.getTraceId(); }
    private void pasteTraceId() { ThreadLocalUtil.setTraceId(traceId); }
}
// Register
HystrixPlugins.getInstance().registerCommandExecutionHook(new MyHystrixHook());

Potential race condition: multiple caller threads may overwrite the shared traceId in the hook. To avoid this, Hystrix provides HystrixRequestContext and HystrixRequestVariableDefault.

HystrixRequestContext holds a ThreadLocal of request‑scoped data; HystrixRequestVariableDefault works like a ThreadLocal with get/set backed by the context’s ConcurrentHashMap.

public class MyHystrixHook extends HystrixCommandExecutionHook {
    private HystrixRequestVariableDefault
requestVariable = new HystrixRequestVariableDefault<>();
    @Override
    public
void onStart(HystrixInvokable
commandInstance) {
        HystrixRequestContext.initializeContext();
        copyTraceId();
    }
    @Override
    public
void onExecutionStart(HystrixInvokable
commandInstance) { pasteTraceId(); }
    @Override
    public
void onFallbackStart(HystrixInvokable
commandInstance) { pasteTraceId(); }
    @Override
    public
void onSuccess(HystrixInvokable
commandInstance) {
        HystrixRequestContext.getContextForCurrentThread().shutdown();
        super.onSuccess(commandInstance);
    }
    @Override
    public
Exception onError(HystrixInvokable
commandInstance,
                                 HystrixRuntimeException.FailureType failureType, Exception e) {
        HystrixRequestContext.getContextForCurrentThread().shutdown();
        return super.onError(commandInstance, failureType, e);
    }
    private void copyTraceId() { requestVariable.set(ThreadLocalUtil.getTraceId()); }
    private void pasteTraceId() { ThreadLocalUtil.setTraceId(requestVariable.get()); }
}
// Register
HystrixPlugins.getInstance().registerCommandExecutionHook(new MyHystrixHook());

Hystrix’s internal wrapper copies the caller’s HystrixRequestContext to the execution thread, ensuring the traceId is correctly transferred.

Summary : Choose between HystrixConcurrencyStrategy (simpler, works when fallback does not need context) and HystrixCommandExecutionHook with HystrixRequestContext (more robust, covers fallback). Both plugins must be registered before use.

JavaConcurrencythreadlocalHystrixContextPropagation
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

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.