Understanding java.lang.IllegalStateException: Cannot call sendError() after the response has been committed
Issue Analsyis
The error java.lang.IllegalStateException: Cannot call sendError() after the response has been committed occurs when the response has already been committed (e.g., via sendRedirect) and then another attempt is made to modify it (e.g., via sendError).
Example Code
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String uri = request.getRequestURI();
if(pathMatcher.match("/", uri)) {
System.err.println("Redirecting");
response.sendRedirect("/swagger-ui.html");
// return false; // If not returning false, Spring MVC will continue processing the "/" path, causing multiple response commits.
}
return true;
}
Note: Acessing the root path "/" here returns a 404 error.
How DispatcherServlet.doDispatch() Cals Interceptor preHandle
// If the interceptor's preHandle returns false, the method exits immediately.
if(!mappedHandler.applyPreHandle(request,response)){
return ;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// From this, if preHandle returns false, the handler (controller method) is not invoked.
mappedHandler is a HandlerExecutionChain object returned by HandlerMapping. It contains a handler (processor object) and an array of interceptors. The applyPreHandle method calls preHandle for each interceptor in the array.
// HandlerExecutionChain class
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}
Root Cause of the Error
-
The interceptor intercepts requests to the root path "/" and calls
response.sendRedirect("/swagger-ui.html"), which commits the response. Since the interceptor does not returnfalse, Spring MVC continues processing the "/" path. -
When no handler is found for "/", Spring MVC defaults to using
ResourceHttpRequestHandlerfor request handling.ResourceHttpRequestHandlerperforms a 404 check. If the path or resource does not exist, it callsresponse.sendError(HttpServletResponse.SC_NOT_FOUND). The source code is as follows:
// ResourceHttpRequestHandler 404 check
// For very general mappings (e.g. "/") we need to check 404 first
Resource resource = getResource(request);
if (resource == null) {
logger.debug("Resource not found");
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
- As a result, since the response was already committed by
sendRedirect, callingsendErrorleads to theIllegalStateException.
Solutions
-
Solution 1: Return
falseafter callingsendRedirectto stop further processing.@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri = request.getRequestURI(); if(pathMatcher.match("/", uri)) { response.sendRedirect("/swagger-ui.html"); return false; // Prevents further processing } return true; } -
Solution 2: Change the interceptor path from "/" to a path that is handled by an error controller (e.g., "/error").
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri = request.getRequestURI(); if(pathMatcher.match("/error", uri)) { response.sendRedirect("/swagger-ui.html"); } return true; }
Log Examples
Example 1: Requesting /api/test/error (no handler, 404):
2019-02-20 14:55:57.086 DEBUG 2676 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/api/test/error", parameters={}
2019-02-20 14:55:57.092 DEBUG 2676 --- [nio-8080-exec-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/", "/"]
2019-02-20 14:55:57.094 DEBUG 2676 --- [nio-8080-exec-1] o.s.w.s.r.ResourceHttpRequestHandler : Resource not found
2019-02-20 14:55:57.095 DEBUG 2676 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 404 NOT_FOUND
Example 2: Requesting /api/test/error1 (handler exists but throws exception):
2019-02-20 15:13:23.860 DEBUG 2676 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : GET "/api/test/error1", parameters={}
2019-02-20 15:13:23.861 DEBUG 2676 --- [nio-8080-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.lwt.vo.Result<String> org.lwt.controller.RoleController.ErrorTest()
// Interceptor called
2019-02-20 15:13:23.865 DEBUG 2676 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Failed to complete request: org.joda.time.IllegalInstantException: custom exception
Example 3: Requesting /api/test/error1 (successful):
2019-02-20 15:21:31.440 DEBUG 8252 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/api/test/error1", parameters={}
2019-02-20 15:21:31.444 DEBUG 8252 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.lwt.vo.Result<String> org.lwt.controller.RoleController.ErrorTest()
// Interceptor uri: /api/test/error1
2019-02-20 15:21:31.473 DEBUG 8252 --- [nio-8080-exec-1] m.m.a.RequestResponseBodyMethodProcessor : Writing [org.lwt.vo.Result@334348d5]
2019-02-20 15:21:31.486 DEBUG 8252 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK