Spring Boot 3.1.4并发请求丢失PATH属性致500错误求助
Expected parsed RequestPath in request attribute异常的排查思路 我们的Java应用基于Spring Boot 3.1.4和Spring 6.0.12,当客户端并发发送相同请求时,其中一个请求会处理失败并返回500状态码,对应的错误日志如下:
java.lang.IllegalArgumentException: Expected parsed RequestPath in request attribute "org.springframework.web.util.ServletRequestPathUtils.PATH". at org.springframework.util.Assert.notNull(Assert.java:222) at org.springframework.web.util.ServletRequestPathUtils.getParsedRequestPath(ServletRequestPathUtils.java:77) at org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition.getMatchingCondition(PathPatternsRequestCondition.java:192) at org.springframework.web.servlet.mvc.method.RequestMappingInfo.getMatchingCondition(RequestMappingInfo.java:407) at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getMatchingMapping(RequestMappingInfoHandlerMapping.java:110) at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getMatchingMapping(RequestMappingInfoHandlerMapping.java:68) at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.addMatchingMappings(AbstractHandlerMethodMapping.java:447) at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:407) at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:382) at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:126) at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:68) at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:505) at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1275) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1057) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:527) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614) at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:110) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:67) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:365) at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126) at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCache
已知该ServletRequestPathUtils.PATH属性应在DispatcherServlet::doDispatch执行前设置,但实际在PathPatternsRequestCondition::getMatchingCondition调用前丢失,以下是具体排查思路:
排查自定义过滤器或拦截器对请求属性的修改
检查所有自定义Filter或HandlerInterceptor,确认是否存在并发场景下错误清除/覆盖请求属性的逻辑。重点关注实现OncePerRequestFilter的过滤器,查看其doFilterInternal方法中是否有操作request.setAttribute或request.removeAttribute的代码,尤其是针对ServletRequestPathUtils.PATH相关的操作。验证请求对象的线程安全性
排查是否存在将HttpServletRequest对象传递到异步线程/线程池处理的情况。Servlet规范中HttpServletRequest并非线程安全,若并发场景下被多线程共享,会导致属性被意外修改或清除。检查异步处理逻辑(如@Async方法、DeferredResult、Callable)是否直接持有请求对象引用,且未通过RequestContextHolder正确获取上下文。检查Spring Security过滤器链的异常分支处理
从日志可见请求经过Spring Security过滤器链,排查Security过滤器的异常分支(如认证失败、权限拦截)是否导致请求属性被意外清除。例如RequestCacheAwareFilter等过滤器在处理请求缓存时,是否错误重置了请求属性。可临时禁用部分Security过滤器(仅用于排查),定位具体问题过滤器。排查容器层面的请求复用问题
当前应用使用Undertow作为Servlet容器,检查Undertow配置是否开启请求对象复用(如buffer-cache或request-pool相关配置)。若容器在并发场景下复用请求对象但未正确清理属性,会导致前一个请求的属性被清除后,后续请求未重新设置。可尝试禁用请求复用配置,观察问题是否消失。调试追踪属性的设置与清除时机
通过断点调试或日志打印,追踪ServletRequestPathUtils.PATH属性的生命周期:- 在
DispatcherServlet::doDispatch方法开头,打印属性是否存在; - 在每个过滤器的
doFilter方法前后,打印属性状态; - 在
PathPatternsRequestCondition::getMatchingCondition方法前,再次打印属性状态。
对比不同请求的日志,定位属性被清除的具体阶段和代码位置。
- 在
检查Spring框架的版本兼容性与已知bug
确认Spring Boot 3.1.4和Spring 6.0.12是否存在相关并发bug。查看Spring官方issue tracker,搜索ServletRequestPathUtils.PATH、concurrency等关键词,若存在已知bug,考虑升级到修复后的版本。
内容的提问来源于stack exchange,提问作者ryan

