dromara/Sa-Token · error · RuntimeException

当前线程非JavaWeb环境

Error message

当前线程非JavaWeb环境

What it means

Thrown by SoMap.getRequestSoMap() when Spring's RequestContextHolder.getRequestAttributes() returns null on the current thread — meaning the call is not executing inside an HTTP request bound by Spring MVC. The static helper relies on SpringMVC's thread-local request context, which only exists on request-handler threads.

Source

Thrown at sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/utils/SoMap.java:578

//		try {
//			return JSON.toJSONString(this, true); 
//		} catch (Exception e) {
//			throw new RuntimeException(e);
//		}
//	}

	// ============================= web辅助 =============================


	/**
	 * 返回当前request请求的的所有参数 
	 * @return
	 */
	public static SoMap getRequestSoMap() {
		// 大善人SpringMVC提供的封装 
		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		if(servletRequestAttributes == null) {
			throw new RuntimeException("当前线程非JavaWeb环境");
		}
		// 当前request
		HttpServletRequest request = servletRequestAttributes.getRequest(); 
		if (request.getAttribute("currentSoMap") == null || request.getAttribute("currentSoMap") instanceof SoMap == false ) {
			initRequestSoMap(request);
		}
		return (SoMap)request.getAttribute("currentSoMap");
	}

	/** 初始化当前request的 SoMap */
	private static void initRequestSoMap(HttpServletRequest request) {
		SoMap soMap = new SoMap();
		Map<String, String[]> parameterMap = request.getParameterMap();	// 获取所有参数 
		for (String key : parameterMap.keySet()) {
			try {
				String[] values = parameterMap.get(key); // 获得values 
				if(values.length == 1) {
					soMap.set(key, values[0]);

View on GitHub (pinned to ac2c7f6e94)

Solutions

  1. Move the getRequestSoMap() call onto the HTTP request thread (inside the controller) and pass the SoMap/values as arguments to the async code.
  2. For tests: RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest()));
  3. If truly needed on other threads, capture RequestContextHolder.setRequestAttributes(attributes, true) (inheritable) before spawning — but explicit parameter passing is safer.
  4. Check that spring-boot-starter-web (not just webflux) is on the classpath so ServletRequestAttributes is what gets bound.

Example fix

// before
@Async
public void process() {
    SoMap soMap = SoMap.getRequestSoMap(); // throws
}

// after
public void handle(HttpServletRequest request) {
    SoMap soMap = SoMap.getRequestSoMap();
    process(soMap.getString("code"));
}

@Async
public void process(String code) { ... }
Defensive patterns

Strategy: validation

Validate before calling

ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs == null) {
    // not on a request thread: pass values explicitly instead of calling getRequestSoMap()
}

Type guard

boolean isRequestThread() { return RequestContextHolder.getRequestAttributes() != null; }

Prevention

When it happens

Trigger: Calling getRequestSoMap() (or any code path that uses it) from: a @Scheduled method, an @Async method without context propagation, a newly spawned thread / CompletableFuture lambda, a Filter running before RequestContextFilter is registered, or a unit test that never set up MockHttpServletRequest via RequestContextHolder.setRequestAttributes(...).

Common situations: Refactoring a controller helper into a background job; running demos under Servlet 3 async dispatch where the context is not propagated; plain junit tests calling controller logic directly.

Related errors


AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14). Data as JSON: /api/errors/45b3fac837787fd4. Report an issue: GitHub.