๐Ÿ”ด The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOG2026-02-20 13:40:05.200 ERROR 8842 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] threw exception java.lang.IllegalStateException: Cannot call sendError() after the response has been committed at org.apache.catalina.connector.ResponseFacade.sendError(ResponseFacade.java:475) at jakarta.servlet.http.HttpServletResponseWrapper.sendError(HttpServletResponseWrapper.java:141)

โšก Quick Fix Works 80% of the time

Do not write to the HttpServletResponse directly before throwing an exception.

// BAD response.getWriter().write("Hello"); throw new RuntimeException(); // GOOD return ResponseEntity.ok("Hello");

๐Ÿง  Why this Happens

Tap to expand the deep technical explanation

An HTTP response is 'committed' when the HTTP headers (and possibly some body content) have already been sent over the network to the client. In your code, an exception occurred AFTER this point. Spring tried to intercept the exception and return a 500 Error JSON, but it cannot modify the response anymore because it was already sent.

The HITEC City Parking Spot Analogy:

It's like trying to unsend a text message after it has already been delivered and read. The network has committed the data, and you cannot take it back or change the message.

๐Ÿ” How to Reproduce Confirm this is your error

In a controller method, call `response.getWriter().write("Hello"); response.getWriter().flush();`. Immediately after, `throw new RuntimeException()`. Spring will try to render the error page but fail.

๐Ÿ› ๏ธ Solutions (5 Ways to Fix)

Solution 1โœ“ Most common cause

Return ResponseEntity instead of using HttpServletResponse

๐Ÿ‘‰ Use this as the modern Spring MVC best practice.

Spring manages the response buffer. If you return an object or ResponseEntity, Spring waits until the method finishes successfully before committing the response.

@GetMapping("/data") public ResponseEntity<String> getData() { // No direct response manipulation return ResponseEntity.ok("Hello"); }
Solution 2

Do not flush streams manually

๐Ÿ‘‰ Use this if you are manually writing to OutputStreams.

Calling `flush()` forces the server to commit the headers and send the bytes immediately. Let Spring/Tomcat handle flushing.

// Remove this line: // response.getWriter().flush();
Solution 3

Handle file downloads carefully

๐Ÿ‘‰ Use this if you are streaming large files.

If an error occurs halfway through a streaming download, you can't send an error JSON. Wrap the stream in a try-catch and just log the error.

try (InputStream is = new FileInputStream(file)) { StreamUtils.copy(is, response.getOutputStream()); } catch (IOException e) { // Cannot send 500 error now, just log it log.error("File download interrupted", e); }
Solution 4

Use @RestControllerAdvice for errors

๐Ÿ‘‰ Use this to catch exceptions globally before they hit the servlet layer.

Global exception handlers intercept the exception before the response is committed, allowing you to return clean JSON.

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<String> handleAll(Exception e) { return ResponseEntity.status(500).body("Error"); } }
Solution 5

Check for forward/include loops

๐Ÿ‘‰ Use this if the error occurs during view rendering (JSP/Thymeleaf).

If a view throws an error after the template engine started writing output, the response is committed. Catch errors in the controller before rendering.

// Validate data in the controller BEFORE adding to Model if (user == null) throw new RuntimeException(); model.addAttribute("user", user); return "view";

๐Ÿ“‹ Version Notes

Spring Boot 2.x

Uses javax.servlet.

Spring Boot 3.x

Uses jakarta.servlet. Stricter commit rules in Tomcat 10+.

๐Ÿ›ก๏ธ How to Prevent This Next Time

Never use `HttpServletResponse` directly in a `@RestController`. Always return objects and let Spring handle the HTTP response buffer.

Course Search
Search across all chapters & stages
๐Ÿ“–

Search the course

Type any topic โ€” branching, stash, rebase, hooks โ€” and jump straight to that chapter.

merge branchesgit stashundo commitrebase