Backend Development 13 min read

Elegant Methods for Passing Data Between Parent and Child Threads in Spring Boot

This article explains four techniques—including manual setting, TaskDecorator, InheritableThreadLocal, and TransmittableThreadLocal—to reliably transfer user context and other thread‑local data from parent to child threads in Spring Boot asynchronous execution, with complete code samples and best‑practice recommendations.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Elegant Methods for Passing Data Between Parent and Child Threads in Spring Boot

Spring Boot developers often need to propagate data such as user information or trace IDs from a parent thread to its asynchronous child threads. This guide presents four practical solutions.

1. Manual Setting

Retrieve the LoginVal from the parent thread and explicitly set it in the child thread before execution.

/**
 * @author 公众号:码猿技术专栏
 * @description 用户上下文信息
 */
public class OauthContext {
    private static final ThreadLocal
loginValThreadLocal = new ThreadLocal<>();
    public static LoginVal get() { return loginValThreadLocal.get(); }
    public static void set(LoginVal loginVal) { loginValThreadLocal.set(loginVal); }
    public static void clear() { loginValThreadLocal.remove(); }
}

public void handlerAsync() {
    //1. 获取父线程的loginVal
    LoginVal loginVal = OauthContext.get();
    log.info("父线程的值:{}", OauthContext.get());
    CompletableFuture.runAsync(() -> {
        //2. 设置子线程的值,复用
        OauthContext.set(loginVal);
        log.info("子线程的值:{}", OauthContext.get());
    });
}

While functional, this approach requires repetitive code for every asynchronous task.

2. ThreadPool TaskDecorator

Implement TaskDecorator to capture the parent context and automatically set it in each task.

/**
 * @author 公众号:码猿技术专栏
 * @description 上下文装饰器
 */
public class ContextTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        //获取父线程的loginVal
        LoginVal loginVal = OauthContext.get();
        return () -> {
            try {
                // 将主线程的请求信息,设置到子线程中
                OauthContext.set(loginVal);
                // 执行子线程,这一步不要忘了
                runnable.run();
            } finally {
                // 线程结束,清空这些信息,防止内存泄漏
                OauthContext.clear();
            }
        };
    }
}

@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
    poolTaskExecutor.setCorePoolSize(xx);
    poolTaskExecutor.setMaxPoolSize(xx);
    poolTaskExecutor.setKeepAliveSeconds(xx);
    poolTaskExecutor.setQueueCapacity(xx);
    poolTaskExecutor.setTaskDecorator(new ContextTaskDecorator());
    poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
    return poolTaskExecutor;
}

public void handlerAsync() {
    log.info("父线程的用户信息:{}", OauthContext.get());
    CompletableFuture.runAsync(() ->
        log.info("子线程的用户信息:{}", OauthContext.get()), taskExecutor);
}

All business code can now rely on the context being automatically available in child threads.

3. InheritableThreadLocal

Replace ThreadLocal with InheritableThreadLocal so that child threads inherit the value. This method is simple but can cause stale data when thread pools reuse threads.

/**
 * @author 公众号:码猿技术专栏
 * @description 用户上下文信息
 */
public class OauthContext {
    private static final InheritableThreadLocal
loginValThreadLocal = new InheritableThreadLocal<>();
    public static LoginVal get() { return loginValThreadLocal.get(); }
    public static void set(LoginVal loginVal) { loginValThreadLocal.set(loginVal); }
    public static void clear() { loginValThreadLocal.remove(); }
}

4. TransmittableThreadLocal (TTL)

TTL, an Alibaba open‑source library, extends InheritableThreadLocal and works correctly with thread‑pool reuse. Add the dependency and replace the context implementation.

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.14.2</version>
</dependency>
public class OauthContext {
    private static final TransmittableThreadLocal
loginValThreadLocal = new TransmittableThreadLocal<>();
    public static LoginVal get() { return loginValThreadLocal.get(); }
    public static void set(LoginVal loginVal) { loginValThreadLocal.set(loginVal); }
    public static void clear() { loginValThreadLocal.remove(); }
}

TTL captures the parent thread’s locals via Transmitter.capture() , wraps tasks with TtlCallable or TtlRunnable , and restores the original state after execution. The core logic resides in TtlCallable#call() , which replays captured values, runs the task, then restores the backup.

Summary

Four approaches are presented; the author recommends using TaskDecorator (method 2) or TransmittableThreadLocal (method 4) for production because they avoid manual boilerplate and handle thread‑pool reuse safely.

BackendJavaasynchronousSpring BootthreadlocalTransmittableThreadLocalTaskDecorator
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.