dromara/Sa-Token · error · SaTokenContextException
10002
10002
Error message
SaTokenContext 上下文尚未初始化
What it means
SaTokenContextForThreadLocalStaff keeps the current request's model box in a ThreadLocal. getModelBox() throws code 10002 when the ThreadLocal is empty for the calling thread, i.e. setContext() was never called on this thread. Unlike the CODE_10001 errors (no integration at all), this one means the integration exists but the current thread is outside any bound request context.
Source
Thrown at sa-token-core/src/main/java/cn/dev33/satoken/context/SaTokenContextForThreadLocalStaff.java:73
modelBoxThreadLocal.remove();
}
/**
* 获取当前线程的 [ Box 存储器 ]
* @return /
*/
public static SaTokenContextModelBox getModelBoxOrNull() {
return modelBoxThreadLocal.get();
}
/**
* 获取当前线程的 [ Box 存储器 ], 如果为空则抛出异常
* @return /
*/
public static SaTokenContextModelBox getModelBox() {
SaTokenContextModelBox box = modelBoxThreadLocal.get();
if(box == null) {
throw new SaTokenContextException("SaTokenContext 上下文尚未初始化").setCode(SaErrorCode.CODE_10002);
}
return box;
}
/**
* 在当前线程的 SaRequest 包装对象
*
* @return /
*/
public static SaRequest getRequest() {
return getModelBox().getRequest();
}
/**
* 在当前线程的 SaResponse 包装对象
*
* @return /
*/View on GitHub (pinned to ac2c7f6e94)
Solutions
- Move the sa-token-dependent logic back into the request thread (pass loginId as a method argument into the async code instead of calling StpUtil there)
- If the async code must use sa-token, capture what you need (loginId, token) before leaving the request thread
- In tests or carefully controlled non-web code, set a mock context for the thread before calling SaHolder/StpUtil and clear it after
Example fix
// before
CompletableFuture.runAsync(() -> {
Object id = StpUtil.getLoginId(); // CODE_10002: no context on pool thread
});
// after: capture in the request thread
Object id = StpUtil.getLoginId();
CompletableFuture.runAsync(() -> {
process(id);
}); Defensive patterns
Strategy: try-catch
Validate before calling
// check for an active context before using SaHolder on arbitrary threads
if (SaTokenContextForThreadLocalStaff.getModelBoxOrNull() == null) {
// not inside a request: capture loginId before going async instead
} Try / catch
try {
Object id = StpUtil.getLoginId();
} catch (SaTokenContextException e) {
if (SaErrorCode.CODE_10002 == e.getCode()) {
// thread has no bound context: fall back to a passed-in principal
}
} Prevention
- Pass loginId/token values into async lambdas as parameters
- Do not call StpUtil/SaHolder inside @Async/@Scheduled/new Thread blocks
- Wrap test calls with a mock context and clear it in finally
When it happens
Trigger: Calling SaHolder.getRequest()/getStorage() or StpUtil APIs from @Scheduled jobs, @Async methods, CompletableFuture.supplyAsync, newly spawned threads, or message consumers — threads on which the web filter never ran setContext().
Common situations: Background token cleanup or push jobs that reuse service methods containing StpUtil calls; async processing detached from the HTTP request; unit tests calling StpUtil without a mock context; timer threads in a servlet app.
Related errors
AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14).
Data as JSON: /api/errors/2c809c54e9199c70.
Report an issue: GitHub.