LearnAI ToolsCareerPractice BuildsPlayContact
JSPIntermediate~2 hours

Dynamic Report Page

Render a data table and summary using JSTL core and formatting tags.

JSTLExpression LanguageIncludes

Overview

Early JSP code had an infamous reputation for pages packed with `<% ... %>` scriptlets full of raw Java — loops, conditionals, and business logic all mixed directly into HTML. JSTL (the JSP Standard Tag Library) and Expression Language exist specifically to eliminate that: `<c:forEach>` replaces a scriptlet `for` loop, `<c:if>`/`<c:choose>` replace scriptlet `if`/`else`, and `${...}` EL replaces `<%= ... %>` output expressions, all while keeping the page readable as HTML with a few extra tags rather than as Java wearing an HTML costume.

By the end of this tutorial you will have a `ReportServlet` that builds a `List<ReportRow>` and puts it in request scope, and a `report.jsp` view that renders it as a table using `<c:forEach>`, highlights negative values with `<c:choose>`/`<c:when>`/`<c:otherwise>`, formats currency and dates with the JSTL `fmt` taglib, and pulls in a shared header and footer through `<jsp:include>` so that markup is not duplicated across every page in the app.

What You'll Build
  • A `ReportRow` model class representing one line of a financial report.
  • A `ReportServlet` that builds sample report data and forwards it to the view in request scope.
  • Reusable `header.jspf` and `footer.jspf` fragments pulled in with `<jsp:include>`.
  • A `report.jsp` table built with `<c:forEach>`, with conditional row styling via `<c:choose>`.
  • Currency and date formatting using the JSTL `fmt` taglib's `<fmt:formatNumber>` and `<fmt:formatDate>`.

Prerequisites

  • This course's Student Registration System project — Servlet-populates-model, forwards-to-JSP is reused here unchanged.
  • Expression Language basics — `${...}` for reading a bean property or a collection element.
  • What a JAR dependency is — this project needs the `jstl` and `taglibs-standard` (or Jakarta EE equivalent) library on the classpath for the `<c:...>`/`<fmt:...>` tags to resolve.
  • Basic `List`/POJO familiarity from earlier in this course.

Project Structure

ReportApp/
└── src/main/webapp/
├── report.jsp // Main view: renders the table via JSTL, includes header/footer fragments
├── header.jspf // Reusable fragment: shared page banner
├── footer.jspf // Reusable fragment: shared page footer
└── WEB-INF/
├── lib/
│ └── jakarta.servlet.jsp.jstl-*.jar // JSTL implementation, required for <c:...>/<fmt:...>
└── classes/
├── ReportRow.java // The Model: one row of the report (label, amount, date)
└── ReportServlet.java // The Controller: builds the List<ReportRow> and forwards to report.jsp

The `.jspf` extension on the header/footer fragments is a common convention (not a hard requirement) for "JSP fragment" files that are never requested directly by a browser, only pulled into a parent page with `<jsp:include>` — much like `WEB-INF/` hides the compiled classes from direct requests in the previous two projects.

Step 1: Define the ReportRow Model

`ReportRow` follows the same JavaBean shape from the Student Registration System project — private fields, a no-arg constructor, and getters — because `<c:forEach>` and EL both read a collection's elements the same reflective way `<jsp:getProperty>` reads a single bean's properties. `amount` is deliberately allowed to be negative, since Step 4's conditional row styling exists specifically to highlight that case.

package model;
import java.util.Date;
// A JavaBean, same shape requirement as Student from the registration
// project: EL (${row.label}, ${row.amount}) and <c:forEach> both resolve
// properties through getX() methods via reflection, so this must follow the
// same private-field + no-arg-constructor + getter/setter convention.
public class ReportRow {
private String label; // e.g. "Product Sales", "Refunds"
private double amount; // Can be negative -- report.jsp highlights negative rows in Step 4
private Date date; // When this line item was recorded
public ReportRow() {
// Required no-arg constructor, same reasoning as Student's in the registration project
}
public ReportRow(String label, double amount, Date date) {
this.label = label;
this.amount = amount;
this.date = date;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}

Step 2: Populate Report Data in a Servlet

Just like `RegisterServlet` in the first project, `ReportServlet` is the controller: it never renders HTML, it only builds the model — here a `List<ReportRow>` — and attaches it to request scope before forwarding to the view. A real version of this servlet would build the list from a database query instead of hardcoded sample rows, but the JSP side (Steps 3-5) would not need to change at all, since it only ever depends on `report` being a `List<ReportRow>` in request scope, not on where that list came from.

package controllers;
import model.ReportRow;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/report")
public class ReportServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
List<ReportRow> report = new ArrayList<>();
// Sample data standing in for a real database query -- report.jsp
// only cares that "report" resolves to a List<ReportRow>, never how
// that list was built.
report.add(new ReportRow("Product Sales", 125000.50, dayOfMonth(1)));
report.add(new ReportRow("Service Revenue", 43250.00, dayOfMonth(5)));
report.add(new ReportRow("Refunds", -8900.75, dayOfMonth(10))); // Negative: <c:choose> flags this row in Step 4
report.add(new ReportRow("Operating Costs", -32000.00, dayOfMonth(15))); // Also negative
report.add(new ReportRow("Interest Income", 1200.25, dayOfMonth(20)));
// request scope: this data lives only for this one request/response cycle,
// exactly like the Student bean in the registration project.
request.setAttribute("report", report);
RequestDispatcher dispatcher = request.getRequestDispatcher("/report.jsp");
dispatcher.forward(request, response);
}
// Small helper building a java.util.Date for the given day of the current month
private java.util.Date dayOfMonth(int day) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DAY_OF_MONTH, day);
return cal.getTime();
}
}

Step 3: Build Reusable Header and Footer Fragments

`<jsp:include>` pulls another page's rendered output into the current page at request time, as if it had been typed inline — it is the JSP equivalent of a reusable UI partial. Both `header.jspf` and `footer.jspf` are plain fragments with no `<html>`/`<body>` wrapper of their own, since they are only ever meant to appear inside a larger page that already provides that structure.

<%-- webapp/header.jspf --%>
<!-- Shared banner: included by every page in the app so the header only
ever needs to be edited in one place -->
<header>
<h1>PrograMinds Reports</h1>
<nav><a href="report">Monthly Report</a></nav>
</header>
<hr>
<%-- webapp/footer.jspf --%>
<!-- Shared footer: same reuse reasoning as header.jspf above -->
<hr>
<footer>
<p>Generated by ReportServlet &mdash; JSP Practice Build</p>
</footer>

`<jsp:include>` is dynamic (evaluated on every request, so it always shows the fragment's current, live output) as opposed to the static `<%@ include file="..." %>` directive, which merges the fragment's source into the including page just once, at translation/compile time. For simple, unchanging boilerplate like this header and footer either would work, but `<jsp:include>` is used here since it is the more common convention for fragments a team may want to change independently of the pages that include them.

Step 4: Render the Table With JSTL core

`<c:forEach var="row" items="${report}">` walks the `List<ReportRow>` the servlet placed in request scope, binding each element to the loop variable `row` for the body of the tag — the direct JSTL equivalent of Java's enhanced `for (ReportRow row : report)`. Inside the loop, `<c:choose>`/`<c:when>`/`<c:otherwise>` picks a CSS class for the row based on whether `row.amount` is negative, mirroring an `if`/`else` without a single scriptlet.

<%-- Requires this taglib directive at the top of report.jsp: --%>
<%-- <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> --%>
<table border="1">
<thead>
<tr><th>Item</th><th>Date</th><th>Amount</th></tr>
</thead>
<tbody>
<%-- var="row" is scoped to just this tag body; items="${report}" reads
the List<ReportRow> the servlet attached to request scope --%>
<c:forEach var="row" items="${report}">
<c:choose>
<%-- row.amount < 0 tests the ReportRow's amount property via EL --%>
<c:when test="${row.amount < 0}">
<tr style="color:red;"> <%-- Negative rows: highlighted --%>
<td>${row.label}</td>
<td>${row.date}</td>
<td>${row.amount}</td>
</tr>
</c:when>
<c:otherwise>
<tr> <%-- Non-negative rows: default styling --%>
<td>${row.label}</td>
<td>${row.date}</td>
<td>${row.amount}</td>
</tr>
</c:otherwise>
</c:choose>
</c:forEach>
</tbody>
</table>

The raw `${row.amount}` and `${row.date}` output above is functional but unpolished — a `double` like `-8900.75` renders as a bare number with no currency symbol or thousands separator, and a `Date` renders using its default, not-very-readable `toString()`. Step 5 replaces both with proper `fmt`-tag formatting.

Step 5: Format Numbers and Dates With fmt

The JSTL formatting taglib (prefix `fmt` by convention) adds `<fmt:formatNumber>` for numeric/currency formatting and `<fmt:formatDate>` for date formatting, both of which read from a `value` attribute via EL exactly like `<c:forEach>`'s `items` did. `<c:if test="${not empty report}">` around the whole table additionally guards against rendering an empty `<table>` shell if `report` were ever an empty list.

<%-- Requires this additional taglib directive at the top of report.jsp: --%>
<%-- <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> --%>
<c:if test="${not empty report}">
<table border="1">
<thead>
<tr><th>Item</th><th>Date</th><th>Amount</th></tr>
</thead>
<tbody>
<c:forEach var="row" items="${report}">
<c:choose>
<c:when test="${row.amount < 0}">
<tr style="color:red;">
<td>${row.label}</td>
<td><fmt:formatDate value="${row.date}" pattern="MMM d, yyyy" /></td>
<%-- type="currency" adds the currency symbol and thousands separators automatically --%>
<td><fmt:formatNumber value="${row.amount}" type="currency" /></td>
</tr>
</c:when>
<c:otherwise>
<tr>
<td>${row.label}</td>
<td><fmt:formatDate value="${row.date}" pattern="MMM d, yyyy" /></td>
<td><fmt:formatNumber value="${row.amount}" type="currency" /></td>
</tr>
</c:otherwise>
</c:choose>
</c:forEach>
</tbody>
</table>
</c:if>
<c:if test="${empty report}">
<p>No report data available.</p> <%-- Reached only if the servlet ever forwards an empty list --%>
</c:if>

Complete Code

Here is every file assembled together, including the two taglib directives and the `<jsp:include>` calls that pull in `header.jspf` and `footer.jspf`.

// ---------- WEB-INF/classes/model/ReportRow.java ----------
package model;
import java.util.Date;
public class ReportRow {
private String label;
private double amount;
private Date date;
public ReportRow() {
}
public ReportRow(String label, double amount, Date date) {
this.label = label;
this.amount = amount;
this.date = date;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
// ---------- WEB-INF/classes/controllers/ReportServlet.java ----------
package controllers;
import model.ReportRow;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/report")
public class ReportServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
List<ReportRow> report = new ArrayList<>();
report.add(new ReportRow("Product Sales", 125000.50, dayOfMonth(1)));
report.add(new ReportRow("Service Revenue", 43250.00, dayOfMonth(5)));
report.add(new ReportRow("Refunds", -8900.75, dayOfMonth(10)));
report.add(new ReportRow("Operating Costs", -32000.00, dayOfMonth(15)));
report.add(new ReportRow("Interest Income", 1200.25, dayOfMonth(20)));
request.setAttribute("report", report);
RequestDispatcher dispatcher = request.getRequestDispatcher("/report.jsp");
dispatcher.forward(request, response);
}
private java.util.Date dayOfMonth(int day) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.DAY_OF_MONTH, day);
return cal.getTime();
}
}
<%-- ---------- webapp/report.jsp ---------- --%>
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<title>Monthly Report</title>
</head>
<body>
<jsp:include page="header.jspf" />
<h2>Monthly Financial Report</h2>
<c:if test="${not empty report}">
<table border="1">
<thead>
<tr><th>Item</th><th>Date</th><th>Amount</th></tr>
</thead>
<tbody>
<c:forEach var="row" items="${report}">
<c:choose>
<c:when test="${row.amount < 0}">
<tr style="color:red;">
<td>${row.label}</td>
<td><fmt:formatDate value="${row.date}" pattern="MMM d, yyyy" /></td>
<td><fmt:formatNumber value="${row.amount}" type="currency" /></td>
</tr>
</c:when>
<c:otherwise>
<tr>
<td>${row.label}</td>
<td><fmt:formatDate value="${row.date}" pattern="MMM d, yyyy" /></td>
<td><fmt:formatNumber value="${row.amount}" type="currency" /></td>
</tr>
</c:otherwise>
</c:choose>
</c:forEach>
</tbody>
</table>
</c:if>
<c:if test="${empty report}">
<p>No report data available.</p>
</c:if>
<jsp:include page="footer.jspf" />
</body>
</html>
<!-- ---------- webapp/header.jspf ---------- -->
<header>
<h1>PrograMinds Reports</h1>
<nav><a href="report">Monthly Report</a></nav>
</header>
<hr>
<!-- ---------- webapp/footer.jspf ---------- -->
<hr>
<footer>
<p>Generated by ReportServlet &mdash; JSP Practice Build</p>
</footer>

Sample Run

Request/Response Flow

Click Run to see what this code prints.

Extend This Project

  • Add a totals row at the bottom of the table using `<c:set>` to accumulate a running sum across the `<c:forEach>` loop, formatted with `<fmt:formatNumber type="currency">`.
  • Add a `<c:forEach varStatus="loopStatus">` and use `loopStatus.count` to render an alternating-shade "zebra striped" table instead of only flagging negative rows.
  • Move `header.jspf`/`footer.jspf` into a real JSP tag file (`.tag`) or a `<%@ include %>` directive and compare the tradeoffs against `<jsp:include>`.
  • Add a `<fmt:setLocale>` near the top of `report.jsp` and observe how `<fmt:formatNumber type="currency">` and `<fmt:formatDate>` change output for a different locale.
  • Replace the hardcoded sample data in `ReportServlet` with a real JDBC query against a transactions table.

Summary

You built a fully dynamic report page without a single Java scriptlet in the JSP itself: `<c:forEach>` looped over a `List<ReportRow>` from request scope, `<c:choose>`/`<c:when>`/`<c:otherwise>` picked row styling conditionally, the `fmt` taglib formatted currency and dates properly, and `<jsp:include>` kept the header and footer defined once and reused everywhere. This JSTL-plus-EL style — tags and `${...}` expressions instead of embedded Java — is the standard, maintainable way real JSP views are written today.