{"record":{"id":"e32b65630b36543e","repo":"YunaiV/yudao-cloud","slug":"tenantcontextholder-https-doc-ioco","errorCode":null,"errorMessage":"TenantContextHolder 不存在租户编号！可参考文档：https://doc.iocoder.cn","messagePattern":"TenantContextHolder 不存在租户编号！可参考文档：https://doc\\.iocoder\\.cn","errorType":"exception","errorClass":"NullPointerException","httpStatus":null,"severity":"error","filePath":"yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java","lineNumber":41,"sourceCode":"\n    /**\n     * 获得租户编号\n     *\n     * @return 租户编号\n     */\n    public static Long getTenantId() {\n        return TENANT_ID.get();\n    }\n\n    /**\n     * 获得租户编号。如果不存在，则抛出 NullPointerException 异常\n     *\n     * @return 租户编号\n     */\n    public static Long getRequiredTenantId() {\n        Long tenantId = getTenantId();\n        if (tenantId == null) {\n            throw new NullPointerException(\"TenantContextHolder 不存在租户编号！可参考文档：\"\n                + DocumentEnum.TENANT.getUrl());\n        }\n        return tenantId;\n    }\n\n    public static void setTenantId(Long tenantId) {\n        TENANT_ID.set(tenantId);\n    }\n\n    public static void setIgnore(Boolean ignore) {\n        IGNORE.set(ignore);\n    }\n\n    /**\n     * 当前是否忽略租户\n     *\n     * @return 是否忽略\n     */","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/YunaiV/yudao-cloud/blob/477be9dd49ab7223a972a6abdff0684d6423dec3/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java#L23-L59","documentation":"TenantContextHolder stores the tenant id in a ThreadLocal (set by the tenant filter/interceptor from the 'tenant-id' header). getRequiredTenantId() throws NullPointerException when the ThreadLocal is empty — meaning the executing thread never went through tenant context setup. The message links to the framework's tenant documentation.","triggerScenarios":"Calling getRequiredTenantId() from a thread without tenant context: @Async methods, scheduled jobs, new Thread(...), message listeners, or a request that bypassed TenantContextFilter (missing tenant-id header while the URL is not in the ignore-urls list).","commonSituations":"Adding @Async to a service that reads tenant; Quartz/XXL-Job handlers touching tenant-aware mappers; websocket/SimpV2 threads; forgetting the tenant-id header in API tests; security framework ordering that puts auth before tenant filter incorrectly.","solutions":["Send the tenant-id header (or login first so the token carries tenant) for HTTP calls.","For background/async code, propagate context: TenantUtils.execute(tenantId, () -> ...) or capture and setTenantId(...) in the worker thread (and clear it after).","Add the endpoint to tenant ignore-urls only if it is genuinely tenant-free — otherwise fix context propagation, not the ignore list.","Verify filter order: TenantContextFilter must wrap any component that reads the context."],"exampleFix":"// before\n@Async\npublic void notify(Long userId) {\n    Long tenantId = TenantContextHolder.getRequiredTenantId(); // NPE: no context\n}\n\n// after\n@Async\npublic void notify(Long tenantId, Long userId) {\n    TenantUtils.execute(tenantId, () -> {\n        // tenant-aware work\n    });\n}","handlingStrategy":"validation","validationCode":"Long tenantId = TenantContextHolder.getTenantId();\nif (tenantId == null) {\n    tenantId = resolveTenantFromRequestOrJob(); // or throw a descriptive error\n}\n// only then call getRequiredTenantId()-equivalent logic","typeGuard":null,"tryCatchPattern":"try {\n    Long tenantId = TenantContextHolder.getRequiredTenantId();\n} catch (NullPointerException e) {\n    throw new IllegalStateException(\"No tenant context on thread \" + Thread.currentThread().getName()\n        + \" — wrap work in TenantUtils.execute(tenantId, ...)\");\n}","preventionTips":["Wrap async/job bodies in TenantUtils.execute(tenantId, runnable) and clear context after","Always send the tenant-id header (or use token-based tenant) in API clients","Check TenantContextHolder.getTenantId() for null before any required access in non-web threads"],"tags":["yudao","multi-tenant","threadlocal","async","context"],"backgroundTag":null,"analyzedSha":"477be9dd49ab7223a972a6abdff0684d6423dec3","analyzedAt":"2026-08-14T13:35:31.121Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}