Why do I get the error "IllegalStateException" when using the RequestDispatcher?

Alex Chaffee

When you use RequestDispatcher.forward or RequestDispatcher.include to call another servlet, you must pay very close attention to a fairly tricky detail of the servlet "chain of command."

When a servlet is first called, the response object is fresh and new: its headers have not been set, its buffers are empty, and no data has been written to the client.

However, as soon as either the status code or any of the headers have been written -- or potentially written -- to the client, or when data has been -- or may have been -- written to the body stream, then you may be susceptible to the IllegalStateException error. The problem that this exception is signalling is the new data that you are (or may be) writing is inconsistent with the data that's already been set and then irretrivably sent ("committed") to the client.

Two common variants of this exception are "java.lang.IllegalStateException: Header already sent" and "java.lang.IllegalStateException: Cannot forward as Output Stream or Writer has already been obtained".

"Header already sent" means that one or more headers have been committed to the client, so you can't set that header again.

"Output Stream or Writer has already been obtained" means that since the calling servlet has already called response.getWriter() or response.getOutputStream(), that contaminates the data stream, since the response has been (or may have been) written to already, making it unsuitable for forwarding.

(Some would argue that the exception is overkill; that the Servlet API should just silently log the problem, then continue as best it can, e.g. by simply not writing the new headers or status code or body text. However, the API as it stands is less forgiving, and it throws a hard exception.)

A further complication is "side effects", where methods set the "committed" flag unnecessarily, or at least unexpectedly. For instance, calling response.flushBuffer() sets the "committed" flag (even if the buffer is empty). Furthermore, calling RequestDispatcher.include() calls response.flushBuffer() (even if the "included" servlet doesn't actually write any data to the response). That means that you shouldn't ever call include() before calling forward().

This is due to the semantics of RequestDispatcher.forward() -- it's intended to be called only by servlets that do literally nothing to the response before forwarding (since the "forwardee" is supposed to be the master servlet). Unfortunately, this means that you can't do things that you might naturally expect, like forwarding from one servlet or JSP to another, where each one adds a little bit of data to the response. Also unfortunately, there are some scenarios where you *can* do exactly that, so when you encounter a scenario where the exception is thrown, it seems to come from out of the blue.

What this all means is that the Servlet API is inadequate as a general framework for sending messages among active objects to form a data pipeline. Fortunately, you have an API that is perfectly adequate for that task: Java itself. Structure your application to use JavaBeans and Java method calls. Restrict your Servlets to two types: one type is all data-processing, and the other type is all response-writing.

If you want your response-writers to be modular (one object to build the nav bar, one object to build the banner ad, one object to build the body, one object to build the page footer, etc.), you have two choices:

  1. use JavaBeans or Java method calls to build up the HTML in the response, or
  2. use RequestDispatcher.include() to bring in content from many little servlets from inside a master response-builder servlet.
RequestDispatcher.include() is probably a safer method than RequestDispatcher.forward() for other reasons, since it leaves the request parameters alone (forward() changes the path etc. to be relative to the target servlet, not the original servlet).

You may also look into Servlet 2.3 Filters as an alternative way to string page content together from multiple resources (though it too is quite complicated and ambiguous at times).

See also: