Java Web & Servlets Fundamentals

Request Dispatching: Forward & Include

18 min Lesson 9 of 13

Request Dispatching: Forward & Include

A single HTTP request rarely belongs to just one servlet. Real web applications split work across multiple components: one servlet validates input and prepares data, a JSP renders the HTML, a header fragment is reused across every page. RequestDispatcher is the mechanism that wires these components together server-side, invisibly to the browser. This lesson covers its two operations — forward and include — and the request-attribute channel that carries data between them.

What Is a RequestDispatcher?

A RequestDispatcher is a server-side delegation object. You obtain one by asking the HttpServletRequest or the ServletContext for a target resource path, then you either forward the entire request to that resource or include its output inside your own response. The browser sees one HTTP response with one status code; it has no idea how many components produced it.

There are two ways to obtain a dispatcher:

// 1. From the request — path is relative to the current servlet's context root RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/product.jsp"); // 2. From the ServletContext — same path rules, but useful from helper classes // that do not have a request reference RequestDispatcher rd2 = getServletContext().getRequestDispatcher("/WEB-INF/views/product.jsp");
Prefer request.getRequestDispatcher(). Both methods accept a context-relative path (starting with /). The request-level variant additionally supports paths relative to the current servlet — rarely needed but good to know.

forward() — Handing Off the Request Completely

forward(request, response) transfers full control of the response to the target resource. After the call returns, the calling servlet must write nothing more to the response. If it does, the container will either throw an IllegalStateException or silently discard the extra output, depending on whether the response was already committed.

import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; @WebServlet("/products") public class ProductListServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. Business logic — fetch data List<Product> products = ProductRepository.findAll(); // 2. Pass data to the view via request attributes request.setAttribute("products", products); request.setAttribute("pageTitle", "Our Products"); // 3. Forward — the JSP writes the full response RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/product-list.jsp"); rd.forward(request, response); // DO NOT write to response after this line — the JSP owns it now } }

The JSP at /WEB-INF/views/product-list.jsp receives the same request and response objects, so it can read the attributes placed there by the servlet:

<%-- product-list.jsp --%> <html> <body> <h1>${pageTitle}</h1> <ul> <c:forEach var="p" items="${products}"> <li>${p.name} — $${p.price}</li> </c:forEach> </ul> </body> </html>
Never forward after committing the response. A response is committed once any output has been written or response.flushBuffer() has been called. Calling forward on a committed response throws IllegalStateException. Prepare all attributes, then forward — and make it the very last statement in your handler.

include() — Composing a Page from Parts

include(request, response) delegates to the target resource temporarily. The target writes its output into the same response buffer, then control returns to the calling servlet, which can continue writing. This is perfect for reusable page fragments — headers, footers, navigation bars — that multiple servlets embed into their own output.

@WebServlet("/dashboard") public class DashboardServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); var out = response.getWriter(); // Include the shared header fragment request.getRequestDispatcher("/WEB-INF/fragments/header.jsp") .include(request, response); // This servlet writes its own section out.println("<main>"); out.println(" <h2>Dashboard</h2>"); out.println(" <p>Welcome back!</p>"); out.println("</main>"); // Include the shared footer fragment request.getRequestDispatcher("/WEB-INF/fragments/footer.jsp") .include(request, response); } }
Included resources cannot change response headers or status. The container silently ignores any calls to setContentType(), setStatus(), or sendRedirect() made inside an included resource. Only the top-level servlet controls those. Use include purely for body content fragments.

Passing Data: Request Attributes

Request attributes are the standard courier between a servlet and the resource it dispatches to. They live for the duration of a single request and are stored as a key-value map on the HttpServletRequest object.

// Setting attributes — any serializable object works request.setAttribute("user", currentUser); // a POJO request.setAttribute("errors", validationErrors); // a List<String> request.setAttribute("totalItems", cartItems.size()); // boxed int // Reading them in the target (servlet or JSP) User user = (User) request.getAttribute("user"); // Removing when no longer needed (good hygiene in include chains) request.removeAttribute("temporaryFlag");

In a JSP with EL (Expression Language) and JSTL you read attributes without explicit casting:

<p>Hello, ${user.firstName}!</p> <p>Items in cart: ${totalItems}</p> <c:if test="${not empty errors}"> <ul class="errors"> <c:forEach var="e" items="${errors}"><li>${e}</li></c:forEach> </ul> </c:if>

forward vs include vs sendRedirect — Choosing the Right Tool

  • forward: server-side, same request/response. Use when one servlet prepares data and delegates rendering entirely to a JSP. URL in the browser does not change.
  • include: server-side, same request/response. Use to embed reusable fragments (header, footer, sidebar) into a page assembled by a controlling servlet.
  • sendRedirect: client-side (HTTP 302). Use after POST to prevent form re-submission (Post/Redirect/Get pattern), or to redirect to an external URL. The browser makes a second request, so request attributes are lost.
The MVC pattern maps directly to forward. The servlet is the Controller (validates, calls the Model, stores results in request attributes), and the JSP is the View. forward is the handoff between C and V — this is how classic Jakarta EE MVC works before you introduce a framework like Spring MVC.

Practical Pattern: Front Controller with Forwarding

Many applications route all requests through one entry-point servlet, which decides which sub-handler to delegate to. This is the Front Controller pattern — the same concept Spring MVC's DispatcherServlet implements internally.

@WebServlet("/app/*") public class FrontController extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String path = request.getPathInfo(); // e.g. "/products" or "/orders" String view = switch (path) { case "/products" -> handleProducts(request); case "/orders" -> handleOrders(request); default -> { response.sendError(404); yield null; } }; if (view != null) { request.getRequestDispatcher("/WEB-INF/views/" + view + ".jsp") .forward(request, response); } } private String handleProducts(HttpServletRequest request) { request.setAttribute("products", ProductRepository.findAll()); return "product-list"; } private String handleOrders(HttpServletRequest request) { request.setAttribute("orders", OrderRepository.findAll()); return "order-list"; } }

Summary

RequestDispatcher gives you two server-side delegation tools. Use forward to hand off the complete response to a JSP or another servlet — the servlet prepares data as request attributes, then steps aside. Use include to embed fragment output from another resource into a page you are building. Both operate within the same HTTP request/response cycle, sharing the same attribute map, and both are invisible to the browser. Knowing when to forward, when to include, and when to redirect instead is one of the core routing decisions in any Jakarta EE web application.