spring-projects/spring-security · error · ServletException

Unable to handle the Spring Security Exception because the…

Error message

Unable to handle the Spring Security Exception because the response is already committed.

What it means

ExceptionTranslationFilter catches AuthenticationException/AccessDeniedException so it can start authentication (login redirect) or handle access denial. If the HTTP response is already committed (headers/body flushed to the client), it can no longer send a redirect or error response, so it rethrows the exception wrapped in a ServletException. This prevents silently swallowing a security exception it could not handle.

Solutions

  1. Move authorization checks earlier — before any response content is written (e.g. via authorizeHttpRequests rules instead of in-body checks)
  2. Check response.isCommitted() before writing/flushing early in your handler
  3. If streaming is unavoidable, buffer until authorization completes, or use AuthorizationFilter at the filter level
  4. Catch ServletException downstream only to log; the original security exception is the cause

Example fix

// before
OutputStream out = response.getOutputStream();
stream(file, out); // commits response; later access check throws
// after
if (accessDenied) { throw new AccessDeniedException(...); } // before committing
OutputStream out = response.getOutputStream();
stream(file, out);
Defensive patterns

Strategy: try-catch

Validate before calling

if (response.isCommitted()) {
    log.warn("Response committed; skip triggering security handling");
}

Try / catch

try {
    chain.doFilter(request, response);
} catch (ServletException e) {
    if (e.getCause() instanceof AuthenticationException || e.getCause() instanceof AccessDeniedException) {
        log.warn("Security exception after commit: {}", e.getCause().getMessage());
    } else { throw e; }
}

Prevention

When it happens

Trigger: An AccessDeniedException or AuthenticationException thrown downstream after the response was committed — e.g. streamed content already written, response.flushBuffer() called, or a security check occurring during body streaming inside chain.doFilter().

Common situations: File download/streaming endpoints where authorization is checked mid-stream; large responses that flush before a filter later in the chain throws; JSP pages that write output before a security tag throws.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/89d9f221c7f34645. Report an issue: GitHub.

Appendix: source

Thrown at web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java:144

			chain.doFilter(request, response);
		}
		catch (IOException ex) {
			throw ex;
		}
		catch (Exception ex) {
			// Try to extract a SpringSecurityException from the stacktrace
			Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
			RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
				.getFirstThrowableOfType(AuthenticationException.class, causeChain);
			if (securityException == null) {
				securityException = (AccessDeniedException) this.throwableAnalyzer
					.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
			}
			if (securityException == null) {
				rethrow(ex);
			}
			if (response.isCommitted()) {
				throw new ServletException("Unable to handle the Spring Security Exception "
						+ "because the response is already committed.", ex);
			}
			handleSpringSecurityException(request, response, chain, securityException);
		}
	}

	private void rethrow(Exception ex) throws ServletException {
		// Rethrow ServletExceptions and RuntimeExceptions as-is
		if (ex instanceof ServletException) {
			throw (ServletException) ex;
		}
		if (ex instanceof RuntimeException) {
			throw (RuntimeException) ex;
		}
		// Wrap other Exceptions. This shouldn't actually happen
		// as we've already covered all the possibilities for doFilter
		throw new RuntimeException(ex);
	}

View on GitHub (pinned to 96852e8860)