Student Grade Dashboard with JSTL

Build a scriptlet-free JSP dashboard using EL and JSTL core and fmt tags.

Introduction

30 Second Summary

Every university has a student portal that displays grades, enrollment info, and course data. Behind those portals, Java developers face a choice: embed raw Java code directly into web pages, or use cleaner tools to separate logic from presentation.

In this project, you will build a Student Grade Dashboard that displays student data using Expression Language (EL) and the JSP Standard Tag Library (JSTL). You'll eliminate every line of scriptlet code from your JSP, which is the foundational skill for building maintainable Java web applications.

What You'll Build

A dynamic web dashboard that renders student names, grades, enrollment dates, and courses using colour-coded indicators, formatted numbers, and conditional logic. All powered by EL expressions and JSTL tags with zero scriptlet code.

By the end of this project, you'll have:

  • A Student JavaBean, a servlet, and a JSP dashboard working together in the MVC pattern.
  • Hands-on experience with all major EL implicit objects, operators, and both dot and bracket notation.
  • Working examples of every core JSTL tag for iteration, conditionals, and number/date formatting.
  • Secret Mission: Add interactive search and filtering using the JSTL Functions library.

Are there any prerequisites?

This project assumes you can write a basic servlet and a simple JSP page. You should have JDK 8, Apache NetBeans 13, and Apache Tomcat 9.x already installed on your machine.

Set Up the Web Project and JSTL Library

Before you can build a dashboard, you need the foundation in place. That means a web application project with the right server, the right libraries, and the Java classes that supply data to your JSP.

In this step, you will create a NetBeans web project, add the JSTL library so your JSP can use standard tags, and write the JavaBean and Servlet that prepare student data for display.

In this step, get ready to:
  • Create a Java Web Application project targeting Apache Tomcat 9.x.
  • Add the JSTL 1.2 JAR to the project classpath.
  • Create the Student JavaBean and StudentServlet classes.
Create a new Java Web Application in NetBeans

Every Java web project needs a container (like Apache Tomcat) to run your servlets and JSPs. NetBeans provides a project wizard that wires everything together for you.

  • In NetBeans, click File in the menu bar.
  • Click New Project.
  • In the Categories list, select Java with Ant, then select Web Application in the Projects list.
  • Click Next.
  • Enter GradeDashboard in the Project Name field.
  • Click Next.
  • Select Apache Tomcat 9.x as the server.
  • Select Java EE 7 Web or Java EE 8 Web as the Java EE version.
  • Click Finish.

What did this wizard create?

NetBeans generated a web application structure with a WEB-INF folder, a default index.html, and build configuration that points at your Tomcat server. You now have a deployable (but empty) web project.

You should see the GradeDashboard project appear in the Projects panel on the left side of NetBeans.

Don't see Apache Tomcat in the server list?

Make sure Tomcat 9.x is registered in NetBeans. Go to Tools > Servers > Add Server and point it to your Tomcat installation folder.

Help me register Tomcat 9 in NetBeans

Add the JSTL 1.2 JAR to the project

JSTL does not ship with Tomcat by default. You need to download the library JAR and add it to your project so the JSP engine can find the tag definitions at runtime.

  • Download jstl-1.2.jar from Maven Central. This single JAR bundles both the JSTL API and implementation.
  • In NetBeans, right-click the GradeDashboard project in the Projects panel.
  • Select Properties.
  • Click Libraries in the left panel of the Properties dialog.
  • Click Add JAR/Folder.
  • Browse to the location where you saved jstl-1.2.jar and select it.
  • Click Open, then click OK to close the Properties dialog.

Verify that the JAR now appears under the Libraries node when you expand your project in the Projects panel.

Why not use Maven for dependency management?

University Java courses typically use Ant-based projects in NetBeans. The "Java with Ant > Web Application" project type is more common in educational settings, and manually adding JARs teaches you exactly where libraries live in a web application.

JAR not showing under Libraries?

Make sure you clicked OK (not Cancel) to close the Properties dialog. If the JAR still does not appear, try right-clicking the project and selecting Resolve Project Problems.

Help me add a JAR to my NetBeans project

Create the Student JavaBean and servlet

The MVC pattern separates data (Model), display (View), and control logic (Controller). Your Student class is the Model, the servlet is the Controller, and dashboard.jsp will be the View.

  • Right-click the Source Packages node in your project.
  • Select New > Java Package.
  • Enter com.gradedashboard.model and click Finish.
  • Right-click the new com.gradedashboard.model package and select New > Java Class.
  • Name the class Student and click Finish.
  • Replace the contents of Student.java with the following code:
package com.gradedashboard.model;

import java.util.Date;
import java.util.List;

// JavaBean representing a single student record
public class Student {
    private String name;
    private String email;
    private double grade;
    private Date enrollmentDate;
    private List<String> courses;

    // No-arg constructor required for JavaBean spec
    public Student() {}

    // Convenience constructor for creating sample data
    public Student(String name, String email, double grade, Date enrollmentDate, List<String> courses) {
        this.name = name;
        this.email = email;
        this.grade = grade;
        this.enrollmentDate = enrollmentDate;
        this.courses = courses;
    }

    // Getter/setter pairs - EL uses these to access properties
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }

    public double getGrade() { return grade; }
    public void setGrade(double grade) { this.grade = grade; }

    public Date getEnrollmentDate() { return enrollmentDate; }
    public void setEnrollmentDate(Date enrollmentDate) { this.enrollmentDate = enrollmentDate; }

    public List<String> getCourses() { return courses; }
    public void setCourses(List<String> courses) { this.courses = courses; }
}

What does this code do?

This is a standard JavaBean. It has private fields, a no-argument constructor, and public getter/setter methods for each property.

EL (Expression Language) relies on these getter methods. When you write ${student.name} in a JSP, the EL engine calls getName() behind the scenes.

  • Save Student.java.

Now create the servlet that prepares student data and forwards it to the JSP.

  • Right-click Source Packages and select New > Java Package.
  • Enter com.gradedashboard.servlet and click Finish.
  • Right-click the new com.gradedashboard.servlet package and select New > Java Class.
  • Name the class StudentServlet and click Finish.
  • Replace the contents of StudentServlet.java with the following code:
package com.gradedashboard.servlet;

import com.gradedashboard.model.Student;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

// Maps this servlet to the /dashboard URL path
@WebServlet("/dashboard")
public class StudentServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // Create sample student data
        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice Johnson", "alice@uni.edu", 92.5,
                new Date(122, 8, 1), Arrays.asList("Java", "Databases", "Web Dev")));
        students.add(new Student("Bob Smith", "bob@uni.edu", 78.3,
                new Date(122, 8, 1), Arrays.asList("Java", "Networks")));
        students.add(new Student("Carol White", "carol@uni.edu", 45.0,
                new Date(123, 1, 15), Arrays.asList("Web Dev", "Security", "AI")));
        students.add(new Student("David Lee", "david@uni.edu", 88.7,
                new Date(123, 1, 15), Arrays.asList("Java", "Cloud Computing")));
        students.add(new Student("Eva Martinez", "eva@uni.edu", 61.2,
                new Date(124, 0, 20), Arrays.asList("Databases", "Web Dev", "Mobile Dev")));

        // Set request-scoped attributes for the JSP to access via EL
        request.setAttribute("students", students);
        request.setAttribute("courseName", "Advanced Web Development");
        request.setAttribute("totalStudents", students.size());
        request.setAttribute("currentDate", new Date());

        // Set session-scoped attributes to demonstrate sessionScope in EL
        HttpSession session = request.getSession();
        session.setAttribute("university", "Tech University");
        session.setAttribute("semester", "Semester 1, 2026");

        // Forward to the JSP view (no Java code in the JSP itself)
        request.getRequestDispatcher("/dashboard.jsp").forward(request, response);
    }
}

What does this servlet do?

The @WebServlet("/dashboard") annotation maps this servlet to the /dashboard URL. No web.xml configuration is needed because Servlet 3.0+ supports annotation-based mapping.

The servlet creates sample student data, stores it in request and session scopes using setAttribute(), then forwards to dashboard.jsp. The JSP will access all this data using EL expressions like ${students} and ${sessionScope.university}.

Why does Date use numbers like 122 and 8?

The Date constructor uses year-1900 format. So 122 means 2022, 123 means 2023, and 124 means 2024. The month is zero-indexed, so 8 is September and 0 is January.

This constructor is deprecated in modern Java, but it keeps the sample data simple for a JSTL learning project without introducing java.time complexity.

  • Save StudentServlet.java.

Finally, create a minimal JSP that the servlet forwards to.

  • Right-click the Web Pages folder in your project.
  • Select New > JSP.
  • Enter dashboard as the file name and click Finish.
  • Replace the contents of dashboard.jsp with this minimal HTML shell:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>Student Grade Dashboard</title>
</head>
<body>
</body>
</html>

What does this JSP do right now?

This is a placeholder. It renders a blank page. In the next steps, you will fill the <body> with EL expressions and JSTL tags to display the student data the servlet prepared.

  • Save dashboard.jsp.
  • Right-click the GradeDashboard project and select Run.
  • Once the browser opens, navigate to http://localhost:8080/GradeDashboard/dashboard.

You should see a blank white page with no errors. This confirms the servlet successfully forwarded to the JSP.

Seeing a 404 or 500 error?

A 404 means the URL path does not match /dashboard. Check that your @WebServlet("/dashboard") annotation is spelled correctly and the project name in the URL matches GradeDashboard.

A 500 error usually means a compilation problem. Check the NetBeans Output window for red error messages. Make sure your Student.java import in the servlet matches the package name com.gradedashboard.model.

Help me fix my servlet deployment error

✔️ Awesome, I've got everything!

Great. Double check you have saved all three files before moving on.

ⓧ I'd like to double check the full code

Here is the complete code for each file at this point.

package com.gradedashboard.model;

import java.util.Date;
import java.util.List;

public class Student {
    private String name;
    private String email;
    private double grade;
    private Date enrollmentDate;
    private List<String> courses;

    public Student() {}

    public Student(String name, String email, double grade, Date enrollmentDate, List<String> courses) {
        this.name = name;
        this.email = email;
        this.grade = grade;
        this.enrollmentDate = enrollmentDate;
        this.courses = courses;
    }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }

    public double getGrade() { return grade; }
    public void setGrade(double grade) { this.grade = grade; }

    public Date getEnrollmentDate() { return enrollmentDate; }
    public void setEnrollmentDate(Date enrollmentDate) { this.enrollmentDate = enrollmentDate; }

    public List<String> getCourses() { return courses; }
    public void setCourses(List<String> courses) { this.courses = courses; }
}
package com.gradedashboard.servlet;

import com.gradedashboard.model.Student;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/dashboard")
public class StudentServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice Johnson", "alice@uni.edu", 92.5,
                new Date(122, 8, 1), Arrays.asList("Java", "Databases", "Web Dev")));
        students.add(new Student("Bob Smith", "bob@uni.edu", 78.3,
                new Date(122, 8, 1), Arrays.asList("Java", "Networks")));
        students.add(new Student("Carol White", "carol@uni.edu", 45.0,
                new Date(123, 1, 15), Arrays.asList("Web Dev", "Security", "AI")));
        students.add(new Student("David Lee", "david@uni.edu", 88.7,
                new Date(123, 1, 15), Arrays.asList("Java", "Cloud Computing")));
        students.add(new Student("Eva Martinez", "eva@uni.edu", 61.2,
                new Date(124, 0, 20), Arrays.asList("Databases", "Web Dev", "Mobile Dev")));

        request.setAttribute("students", students);
        request.setAttribute("courseName", "Advanced Web Development");
        request.setAttribute("totalStudents", students.size());
        request.setAttribute("currentDate", new Date());

        HttpSession session = request.getSession();
        session.setAttribute("university", "Tech University");
        session.setAttribute("semester", "Semester 1, 2026");

        request.getRequestDispatcher("/dashboard.jsp").forward(request, response);
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>Student Grade Dashboard</title>
</head>
<body>
</body>
</html>

Your project skeleton is ready. Next up, you will fill that empty JSP with powerful EL expressions that pull data directly from the servlet's attributes.

Master EL Expressions and Implicit Objects

Your project skeleton is ready with a servlet that prepares student data and forwards to a JSP.

The next challenge is displaying that data without writing a single <%= %> scriptlet. Expression Language (EL) lets you pull values from beans, scopes, and request parameters using a clean ${...} syntax.

In this step, get ready to:
  • Write EL expressions using dot and bracket notation to access bean properties, Maps, and Lists.
  • Use EL arithmetic, relational, logical, and empty operators.
  • Display server and request information using EL implicit objects.
Write EL expressions using dot and bracket notation

EL uses dot notation to read JavaBean properties and bracket notation to access List elements or Map keys that contain special characters.

  • In dashboard.jsp, find the empty <body> tag.
  • Replace everything between <body> and </body> with the following code:
<!-- Access session-scoped Map entries with sessionScope -->
<h1>${sessionScope.university} - Grade Dashboard</h1>
<p>Semester: ${sessionScope.semester}</p>

<!-- Access request attributes directly with dot notation -->
<p>Course: ${courseName}</p>
<p>Total Students: ${totalStudents}</p>

<!-- Use bracket notation to access List elements by index -->
<p>First student: ${students[0].name}</p>
<p>First student grade: ${students[0].grade}</p>

What does this code do?

${sessionScope.university} reaches into the session scope to pull the university attribute the servlet set with session.setAttribute().

${courseName} uses EL's implicit search order. It checks page, request, session, then application scope automatically.

${students[0].name} combines bracket notation (for the List index) and dot notation (for the bean property). EL calls getName() behind the scenes.

  • Save dashboard.jsp.
  • Run the project (press F6 in NetBeans) and navigate to http://localhost:8080/GradeDashboard/dashboard.
  • You should see "Tech University - Grade Dashboard" as the heading, with "Alice Johnson" and her grade of 92.5 displayed below.

Seeing a blank page or raw ${...} text?

  • If you see literal ${students[0].name} text on the page, check that your JSP page directive at the top matches <%@ page contentType="text/html;charset=UTF-8" language="java" %>.
  • If the page is blank, confirm the servlet is running by checking the URL is exactly /dashboard (not /dashboard.jsp directly).

Still stuck? Ask for help.

Use EL operators and implicit objects

EL supports arithmetic, relational, logical, and the special empty operator. These let you perform calculations and comparisons directly inside the JSP without needing Java code.

  • In dashboard.jsp, add the following code below the <p>First student grade: ${students[0].grade}</p> line:
<!-- Arithmetic operator: divide the grade by 100 -->
<p>Grade over 100: ${students[0].grade / 100}</p>

<!-- Relational operator: check if grade is at least 50 -->
<p>First student passes: ${students[0].grade >= 50}</p>

<!-- Logical operator: combine two conditions with 'and' -->
<p>First student distinction: ${students[0].grade >= 80 and students[0].grade <= 100}</p>

<!-- Empty operator: checks if a value is null or empty -->
<p>Search param empty: ${empty param.search}</p>

<!-- Implicit object 'param': reads URL query parameters -->
<p>Highlight param: ${param.highlight}</p>

What does this code do?

  • ${students[0].grade / 100} performs division right in the page. EL also supports +, -, *, and % (mod).
  • ${students[0].grade >= 50} evaluates to true or false. You can also write eq, ne, lt, gt as word equivalents.
  • ${empty param.search} returns true when the URL has no search parameter (or it is blank).
  • ${param.highlight} reads the highlight query parameter straight from the URL. The param implicit object is a Map of all request parameters.
  • Save dashboard.jsp.
  • Refresh the browser page at http://localhost:8080/GradeDashboard/dashboard.
  • You should see "Grade over 100: 0.925", "First student passes: true", "First student distinction: true", and "Search param empty: true".

Seeing an error instead of true/false?

  • Check that your comparison uses >= and <= (not => or =<).
  • Make sure and is lowercase in the logical expression. EL keywords are case-sensitive.

Need help debugging EL operators?

Display server and request information using EL implicit objects

EL provides implicit objects that expose the full request environment. The pageContext implicit object gives you access to the underlying servlet request, while header is a Map of all HTTP headers.

  • In dashboard.jsp, add the following code below the <p>Highlight param: ${param.highlight}</p> line:
<!-- pageContext.request exposes the HttpServletRequest object -->
<p>Request URI: ${pageContext.request.requestURI}</p>
<p>Server: ${pageContext.request.serverName}:${pageContext.request.serverPort}</p>
<p>Protocol: ${pageContext.request.protocol}</p>

<!-- header implicit object reads HTTP request headers -->
<p>Host header: ${header["host"]}</p>
<p>User-Agent: ${header["user-agent"]}</p>

What does this code do?

  • ${pageContext.request.requestURI} walks the chain: pageContext gives you the JSP page context, .request gives you the HttpServletRequest, and .requestURI returns the path.
  • ${header["host"]} uses bracket notation because HTTP header names can contain hyphens. Dot notation would fail on user-agent since EL would interpret the hyphen as a minus sign.

Why use bracket notation for headers?

Bracket notation is required whenever a Map key contains characters that are not valid in a Java identifier (like hyphens, dots, or spaces). For simple keys like host, either notation works. For user-agent, only bracket notation is valid.

  • Save dashboard.jsp.
  • In your browser, navigate to http://localhost:8080/GradeDashboard/dashboard?highlight=Alice.
  • You should see "Highlight param: Alice" on the page, along with the Request URI, server name, port, protocol, and your browser's User-Agent string.

Highlight param shows blank?

  • Double-check your URL includes ?highlight=Alice at the end. The param implicit object only reads values from the query string.
  • Make sure the URL starts with /dashboard (the servlet path), not /dashboard.jsp. The servlet must forward the request so that session and request attributes are available.

Need help with EL implicit objects?

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete dashboard.jsp as it should look at this point:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
    <title>Student Grade Dashboard</title>
</head>
<body>
<h1>${sessionScope.university} - Grade Dashboard</h1>
<p>Semester: ${sessionScope.semester}</p>
<p>Course: ${courseName}</p>
<p>Total Students: ${totalStudents}</p>
<p>First student: ${students[0].name}</p>
<p>First student grade: ${students[0].grade}</p>
<p>Grade over 100: ${students[0].grade / 100}</p>
<p>First student passes: ${students[0].grade >= 50}</p>
<p>First student distinction: ${students[0].grade >= 80 and students[0].grade <= 100}</p>
<p>Search param empty: ${empty param.search}</p>
<p>Highlight param: ${param.highlight}</p>
<p>Request URI: ${pageContext.request.requestURI}</p>
<p>Server: ${pageContext.request.serverName}:${pageContext.request.serverPort}</p>
<p>Protocol: ${pageContext.request.protocol}</p>
<p>Host header: ${header["host"]}</p>
<p>User-Agent: ${header["user-agent"]}</p>
</body>
</html>

You now have a JSP that pulls data from beans, scopes, request parameters, and HTTP headers using only EL expressions. Next up, you will add JSTL core tags to build loops, conditionals, and colour-coded grade logic.

Add JSTL Core Tags for Logic and Iteration

In the last step you proved that EL expressions can pull data from beans, scopes, and request parameters. But displaying data is only half the story.

Real dashboards need logic: looping through student lists, colour-coding grades based on thresholds, and conditionally showing banners. JSTL core tags give you all of that without writing a single line of Java scriptlet code inside your JSP.

In this step, get ready to:
  • Add the JSTL core taglib directive and create page-scoped variables with c:set.
  • Use c:if and c:choose to conditionally render content and colour-code grades.
  • Use c:forEach with varStatus to iterate over the students list and display course tags.
Add the taglib directive and use c:set and c:out

Before you can use any JSTL tags, you need to declare a taglib directive at the top of your JSP. This tells the container where to find the tag definitions for the c: prefix.

  • In NetBeans, open dashboard.jsp from the **Web Pages** folder in the Projects panel.
  • Select all the existing content and replace it with the following code:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
    <title>Student Grade Dashboard</title>
</head>
<body>

<h1>${sessionScope.university} - Grade Dashboard</h1>
<p>${sessionScope.semester} | ${courseName}</p>

<!-- Use c:set to create a page-scoped counter variable -->
<c:set var="passCount" value="0" />
<!-- Loop through students to count how many passed -->
<c:forEach var="s" items="${students}">
    <c:if test="${s.grade >= 50.0}">
        <c:set var="passCount" value="${passCount + 1}" />
    </c:if>
</c:forEach>
<p>Total Students: ${totalStudents}</p>
<p>Pass Count: ${passCount}</p>
<p>Current Page: ${pageContext.request.requestURI}</p>

</body>
</html>

What does this code do?

The <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> directive registers the JSTL core library so you can use tags like c:set, c:if, and c:forEach.

The <c:set var="passCount" value="0" /> tag creates a page-scoped variable called passCount. The loop then increments it for each student whose grade is 50 or above. This replaces what would have been a scriptlet counter.

  • Save dashboard.jsp.
  • Run the project and navigate to http://localhost:8080/GradeDashboard/dashboard in your browser.
  • You should see "Pass Count: 4" displayed on the page, confirming that c:set calculated correctly.

Seeing a blank page or an error?

  • Check that the JSTL JARs are still listed under your project's **Libraries** node. Without them, the container cannot resolve the c: prefix.
  • Make sure the taglib URI is exactly http://java.sun.com/jsp/jstl/core with no trailing slash or typo.
  • Confirm that the taglib directive appears on line 2, before the <!DOCTYPE> declaration.

Help me debug my JSTL taglib directive error in dashboard.jsp

Use c:if and c:choose for conditional rendering

Conditional logic is the backbone of any dashboard. The c:if tag renders its body only when a test expression evaluates to true. The c:choose tag works like a switch statement for when you need multiple branches.

  • In dashboard.jsp, find the closing <p>Current Page: ${pageContext.request.requestURI}</p> line.
  • Add the following highlight banner code directly below it (before the closing </body> tag):
<!-- c:if renders this div only when the highlight param exists -->
<c:if test="${not empty param.highlight}">
    <div class="highlight-banner">
        Highlighting student: <c:out value="${param.highlight}" />
    </div>
</c:if>

What does this code do?

The c:if tag checks whether the URL parameter highlight has a value. If you visit the page without ?highlight=... in the URL, this entire div is skipped.

The c:out tag safely outputs user input by escaping HTML characters automatically. This prevents cross-site scripting (XSS) attacks where someone injects malicious <script> tags through URL parameters.

Why use c:out instead of just ${param.highlight}?

A bare EL expression like ${param.highlight} dumps the raw parameter value into the HTML. If someone passes <script>alert('hacked')</script> as the value, the browser would execute it.

The c:out tag escapes < and > characters into safe HTML entities. Always use c:out for any user-supplied value.

  • Save dashboard.jsp.
  • Navigate to http://localhost:8080/GradeDashboard/dashboard?highlight=Alice in your browser.
  • You should see a "Highlighting student: Alice" banner appear on the page.
  • Now visit http://localhost:8080/GradeDashboard/dashboard without the parameter. The banner should disappear completely.

Banner showing even without the URL parameter?

  • Check that your test expression uses not empty (two words, not notempty).
  • Make sure the closing </c:if> tag is present. Missing closing tags can cause the content to always render.

Help me debug my c:if conditional in dashboard.jsp

Use c:forEach for iteration with varStatus

The c:forEach tag iterates over any Java collection. Its varStatus attribute exposes iteration metadata like the current row number, whether this is the first or last item, and more.

  • In dashboard.jsp, find the closing </c:if> tag from the highlight banner you just added.
  • Remove the existing </body> and </html> closing tags.
  • Add the following table structure with c:forEach, c:choose, and the footer below the highlight banner's closing </c:if>:
<!-- Student table using c:forEach with varStatus for row numbers -->
<table>
    <thead>
        <tr>
            <th>#</th>
            <th>Name</th>
            <th>Email</th>
            <th>Grade</th>
            <th>Status</th>
            <th>Enrolled</th>
            <th>Courses</th>
        </tr>
    </thead>
    <tbody>
        <!-- c:forEach iterates the students list; varStatus tracks position -->
        <c:forEach var="student" items="${students}" varStatus="status">
            <tr>
                <!-- status.count gives a 1-based row number -->
                <td>${status.count}</td>
                <!-- c:out escapes HTML in the name for XSS safety -->
                <td><c:out value="${student.name}" /></td>
                <td>${student.email}</td>
                <td>
                    <!-- c:choose works like a switch: first matching c:when wins -->
                    <c:choose>
                        <c:when test="${student.grade >= 80}">
                            <span class="grade-high">${student.grade}%</span>
                        </c:when>
                        <c:when test="${student.grade >= 50}">
                            <span class="grade-mid">${student.grade}%</span>
                        </c:when>
                        <c:otherwise>
                            <span class="grade-low">${student.grade}%</span>
                        </c:otherwise>
                    </c:choose>
                </td>
                <td>
                    <!-- Two c:if tags for mutually exclusive pass/fail -->
                    <c:if test="${student.grade >= 50}">Pass</c:if>
                    <c:if test="${student.grade < 50}">Fail</c:if>
                </td>
                <td>${student.enrollmentDate}</td>
                <td>
                    <!-- Nested c:forEach loops through each student's course list -->
                    <c:forEach var="course" items="${student.courses}">
                        <span class="course-tag">${course}</span>
                    </c:forEach>
                </td>
            </tr>
        </c:forEach>
    </tbody>
</table>

<div class="footer">
    <p>Server: ${pageContext.request.serverName}:${pageContext.request.serverPort} |
       Context: ${pageContext.request.contextPath} |
       Protocol: ${pageContext.request.protocol}</p>
    <p>
        <!-- c:choose in the footer for a class-size label -->
        <c:choose>
            <c:when test="${totalStudents > 3}">
                Large class (${totalStudents} students)
            </c:when>
            <c:otherwise>
                Small class (${totalStudents} students)
            </c:otherwise>
        </c:choose>
    </p>
</div>

</body>
</html>

What does this code do?

  • c:forEach var="student" items="${students}" varStatus="status" loops through the students list. Each iteration exposes the current student as student and the loop metadata as status.
  • ${status.count} gives a 1-based row number. You can also use ${status.first} and ${status.last} to detect the first or last item.
  • c:choose evaluates each c:when in order and renders only the first match. The c:otherwise block runs if nothing else matched (grade below 50).
  • The nested c:forEach var="course" items="${student.courses}" loops through each student's course list to render individual course badges.
  • Save dashboard.jsp.
  • Refresh your browser at http://localhost:8080/GradeDashboard/dashboard.
  • You should see a table with 5 numbered rows, each showing the student name, email, grade with a CSS class name (no colours yet until we add styling in the next step), a Pass/Fail status, enrollment date, and course tags.
  • The footer should display "Large class (5 students)" since totalStudents is greater than 3.

Table not rendering or showing errors?

  • Check that every opening tag has a matching closing tag. The c:forEach, c:choose, and c:if tags all require closing tags.
  • Verify that c:when tags only appear directly inside a c:choose block. Placing them outside causes an error.
  • Make sure items="${students}" matches the attribute name set in your servlet (request.setAttribute("students", students)).

Help me fix my c:forEach table rendering in dashboard.jsp

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete dashboard.jsp as it should look at the end of this step:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
    <title>Student Grade Dashboard</title>
</head>
<body>

<h1>${sessionScope.university} - Grade Dashboard</h1>
<p>${sessionScope.semester} | ${courseName}</p>

<c:set var="passCount" value="0" />
<c:forEach var="s" items="${students}">
    <c:if test="${s.grade >= 50.0}">
        <c:set var="passCount" value="${passCount + 1}" />
    </c:if>
</c:forEach>
<p>Total Students: ${totalStudents}</p>
<p>Pass Count: ${passCount}</p>
<p>Current Page: ${pageContext.request.requestURI}</p>

<c:if test="${not empty param.highlight}">
    <div class="highlight-banner">
        Highlighting student: <c:out value="${param.highlight}" />
    </div>
</c:if>

<table>
    <thead>
        <tr>
            <th>#</th>
            <th>Name</th>
            <th>Email</th>
            <th>Grade</th>
            <th>Status</th>
            <th>Enrolled</th>
            <th>Courses</th>
        </tr>
    </thead>
    <tbody>
        <c:forEach var="student" items="${students}" varStatus="status">
            <tr>
                <td>${status.count}</td>
                <td><c:out value="${student.name}" /></td>
                <td>${student.email}</td>
                <td>
                    <c:choose>
                        <c:when test="${student.grade >= 80}">
                            <span class="grade-high">${student.grade}%</span>
                        </c:when>
                        <c:when test="${student.grade >= 50}">
                            <span class="grade-mid">${student.grade}%</span>
                        </c:when>
                        <c:otherwise>
                            <span class="grade-low">${student.grade}%</span>
                        </c:otherwise>
                    </c:choose>
                </td>
                <td>
                    <c:if test="${student.grade >= 50}">Pass</c:if>
                    <c:if test="${student.grade < 50}">Fail</c:if>
                </td>
                <td>${student.enrollmentDate}</td>
                <td>
                    <c:forEach var="course" items="${student.courses}">
                        <span class="course-tag">${course}</span>
                    </c:forEach>
                </td>
            </tr>
        </c:forEach>
    </tbody>
</table>

<div class="footer">
    <p>Server: ${pageContext.request.serverName}:${pageContext.request.serverPort} |
       Context: ${pageContext.request.contextPath} |
       Protocol: ${pageContext.request.protocol}</p>
    <p>
        <c:choose>
            <c:when test="${totalStudents > 3}">
                Large class (${totalStudents} students)
            </c:when>
            <c:otherwise>
                Small class (${totalStudents} students)
            </c:otherwise>
        </c:choose>
    </p>
</div>

</body>
</html>

Your dashboard now has real logic: loops, conditions, and safe output. Next up, you'll add JSTL formatting tags to display grades with one decimal place and dates in a human-readable format.

Add JSTL Formatting Tags and Final Styling

Your dashboard table is displaying data correctly, but the raw numbers and dates look messy. Grades show long decimal trails like 92.5 and dates appear as Java's default toString() output.

JSTL's formatting library (fmt) gives you tags that format numbers and dates cleanly, without writing any Java code in the JSP. In this step, you'll also add CSS styling to transform the plain HTML into a professional-looking dashboard.

In this step, get ready to:
  • Format grades and pass rate using fmt:formatNumber.
  • Format enrollment dates and the current date using fmt:formatDate.
  • Add CSS styling and restructure the page layout.
Add the fmt taglib directive and format numbers

The fmt tag library handles number and date formatting. You need to declare it at the top of your JSP just like you declared the core library.

  • In dashboard.jsp, find this line near the top:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  • Add the fmt taglib directive directly below it, so both lines look like this:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

What does this directive do?

The <%@ taglib %> directive tells the JSP container where to find the tag library's definition. The prefix fmt is the shorthand you'll use in your tags, and the URI http://java.sun.com/jsp/jstl/fmt points to the JSTL formatting library.

Now let's format the grade numbers. Currently the grade column outputs raw doubles directly.

  • In dashboard.jsp, scroll down to the Grade column inside the <c:forEach> loop.
  • Find the <c:choose> block that colour-codes grades.
  • Replace the entire <td> block for grades with this updated version:
                <td>
                    <c:choose>
                        <c:when test="${student.grade >= 80}">
                            <span class="grade-high">
                                <fmt:formatNumber value="${student.grade}" pattern="#0.0" />%
                            </span>
                        </c:when>
                        <c:when test="${student.grade >= 50}">
                            <span class="grade-mid">
                                <fmt:formatNumber value="${student.grade}" pattern="#0.0" />%
                            </span>
                        </c:when>
                        <c:otherwise>
                            <span class="grade-low">
                                <fmt:formatNumber value="${student.grade}" pattern="#0.0" />%
                            </span>
                        </c:otherwise>
                    </c:choose>
                </td>

What does the pattern #0.0 mean?

The pattern attribute uses Java's DecimalFormat rules. The 0 character forces a digit to appear (showing zero if needed). The # character shows a digit only if one exists.

So #0.0 means: at least one digit before the decimal, exactly one digit after. A grade of 92.5 stays as 92.5, but a grade of 45.0 displays as 45.0 instead of just 45.

Next, let's format the pass rate as a proper percentage.

  • Find this line in dashboard.jsp:
<p>Pass Count: ${passCount}</p>
  • Replace it with a formatted percentage calculation:
<p>Pass Rate: <fmt:formatNumber value="${passCount / totalStudents * 100}" maxFractionDigits="1" />%</p>

What does maxFractionDigits do?

Instead of a fixed pattern, maxFractionDigits="1" caps the decimal places at one. The EL expression ${passCount / totalStudents * 100} calculates the percentage on the fly. fmt:formatNumber then rounds it neatly for display.

  • Save dashboard.jsp.
  • Run the project and navigate to http://localhost:8080/GradeDashboard/dashboard. You should see grades displayed as 92.5%, 78.3%, etc., and the pass rate as a percentage like 80.0%.

Seeing an error about fmt tag?

Make sure the <%@ taglib prefix="fmt" ... %> directive is on its own line directly after the core taglib directive. Check that the URI is exactly http://java.sun.com/jsp/jstl/fmt with no typos.

If you see a "cannot resolve taglib" error, confirm the JSTL JAR is still in your project's Libraries node.

Help me fix a JSTL fmt taglib error in my JSP.

Format dates with fmt:formatDate

Dates are the other half of the formatting library. Right now enrollment dates display as Java's ugly default format. The fmt:formatDate tag lets you specify exactly how dates appear.

  • In dashboard.jsp, find the Enrolled column inside the <c:forEach> loop. Locate this line:
                <td>${student.enrollmentDate}</td>
  • Replace it with a formatted date:
                <td>
                    <fmt:formatDate value="${student.enrollmentDate}" pattern="dd MMM yyyy" />
                </td>

What do the pattern letters mean?

The pattern dd MMM yyyy produces dates like 01 Sep 2022. Each letter has a meaning: dd is day of month (zero-padded), MMM is abbreviated month name, and yyyy is four-digit year.

Now let's add a formatted timestamp for when the dashboard was generated.

  • Find the line that shows the semester info near the top of the body:
<p>${sessionScope.semester} | ${courseName}</p>
  • Add a new line directly below it to display the current date:
<p>Generated: <fmt:formatDate value="${currentDate}" pattern="EEEE, dd MMMM yyyy 'at' HH:mm" /></p>

What does the full date pattern produce?

The pattern EEEE, dd MMMM yyyy 'at' HH:mm produces output like Monday, 09 June 2026 at 14:30. EEEE is the full day name, MMMM is the full month name, and HH:mm is 24-hour time.

Text in single quotes (like 'at') is treated as a literal string and appears as-is in the output.

  • Save dashboard.jsp and refresh the browser.
  • Enrollment dates should now show as 01 Sep 2022 format, and the top of the page should display today's full date and time.

Dates showing incorrectly?

If enrollment dates still show raw output, make sure you replaced the entire <td> element (not just the text inside it). The value attribute must receive a java.util.Date object, which ${student.enrollmentDate} already provides.

Help me debug my fmt:formatDate output in the dashboard.

Add the CSS styling and final page layout

The formatting tags work, but the page still looks like unstyled HTML. In this final chunk, you'll add a CSS stylesheet and wrap the content in semantic div elements to create a professional dashboard layout.

  • In dashboard.jsp, select everything from <!DOCTYPE html> all the way to </html> (leaving the taglib directives at the top untouched).
  • Replace that entire selection with the following structured HTML:
<!DOCTYPE html>
<html>
<head>
    <title>Student Grade Dashboard</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
        .header { background: #2c3e50; color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
        .stats { display: flex; gap: 15px; margin-bottom: 20px; }
        .stat-card { background: white; padding: 15px; border-radius: 8px; flex: 1; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        th { background: #34495e; color: white; padding: 12px; text-align: left; }
        td { padding: 12px; border-bottom: 1px solid #eee; }
        tr:hover { background: #f8f9fa; }
        .grade-high { color: #27ae60; font-weight: bold; }
        .grade-mid { color: #f39c12; font-weight: bold; }
        .grade-low { color: #e74c3c; font-weight: bold; }
        .course-tag { display: inline-block; background: #3498db; color: white; padding: 2px 8px; border-radius: 12px; font-size: 0.8em; margin: 2px; }
        .footer { margin-top: 20px; color: #666; font-size: 0.9em; }
        .highlight-banner { background: #fff3cd; padding: 10px; border-radius: 4px; margin-bottom: 15px; }
    </style>
</head>
<body>

<div class="header">
    <h1>${sessionScope.university} - Grade Dashboard</h1>
    <p>${sessionScope.semester} | ${courseName}</p>
    <p>Generated: <fmt:formatDate value="${currentDate}" pattern="EEEE, dd MMMM yyyy 'at' HH:mm" /></p>
</div>

<div class="stats">
    <div class="stat-card">
        <h3>Total Students</h3>
        <p>${totalStudents}</p>
    </div>
    <div class="stat-card">
        <h3>Pass Rate</h3>
        <c:set var="passCount" value="0" />
        <c:forEach var="s" items="${students}">
            <c:if test="${s.grade >= 50.0}">
                <c:set var="passCount" value="${passCount + 1}" />
            </c:if>
        </c:forEach>
        <p><fmt:formatNumber value="${passCount / totalStudents * 100}" maxFractionDigits="1" />%</p>
    </div>
    <div class="stat-card">
        <h3>Current Page</h3>
        <p>${pageContext.request.requestURI}</p>
    </div>
</div>

<c:if test="${not empty param.highlight}">
    <div class="highlight-banner">
        Highlighting student: <c:out value="${param.highlight}" />
    </div>
</c:if>

<table>
    <thead>
        <tr>
            <th>#</th>
            <th>Name</th>
            <th>Email</th>
            <th>Grade</th>
            <th>Status</th>
            <th>Enrolled</th>
            <th>Courses</th>
        </tr>
    </thead>
    <tbody>
        <c:forEach var="student" items="${students}" varStatus="status">
            <tr>
                <td>${status.count}</td>
                <td><c:out value="${student.name}" /></td>
                <td>${student.email}</td>
                <td>
                    <c:choose>
                        <c:when test="${student.grade >= 80}">
                            <span class="grade-high">
                                <fmt:formatNumber value="${student.grade}" pattern="#0.0" />%
                            </span>
                        </c:when>
                        <c:when test="${student.grade >= 50}">
                            <span class="grade-mid">
                                <fmt:formatNumber value="${student.grade}" pattern="#0.0" />%
                            </span>
                        </c:when>
                        <c:otherwise>
                            <span class="grade-low">
                                <fmt:formatNumber value="${student.grade}" pattern="#0.0" />%
                            </span>
                        </c:otherwise>
                    </c:choose>
                </td>
                <td>
                    <c:if test="${student.grade >= 50}">Pass</c:if>
                    <c:if test="${student.grade < 50}">Fail</c:if>
                </td>
                <td>
                    <fmt:formatDate value="${student.enrollmentDate}" pattern="dd MMM yyyy" />
                </td>
                <td>
                    <c:forEach var="course" items="${student.courses}">
                        <span class="course-tag">${course}</span>
                    </c:forEach>
                </td>
            </tr>
        </c:forEach>
    </tbody>
</table>

<div class="footer">
    <p>Server: ${pageContext.request.serverName}:${pageContext.request.serverPort} |
       Context: ${pageContext.request.contextPath} |
       Protocol: ${pageContext.request.protocol}</p>
    <p>
        <c:choose>
            <c:when test="${totalStudents > 3}">
                Large class (${totalStudents} students)
            </c:when>
            <c:otherwise>
                Small class (${totalStudents} students)
            </c:otherwise>
        </c:choose>
    </p>
</div>

</body>
</html>

What does this layout add?

The <style> block defines all the visual styling. Key classes include .header (dark banner at the top), .stat-card (white cards for summary stats), and the colour classes .grade-high, .grade-mid, .grade-low that your c:choose block already applies.

The stats section uses flexbox (display: flex) to lay out three cards in a row. The table gets rounded corners, hover highlighting, and proper spacing. Course tags become coloured badges with .course-tag.

The pass count calculation moved inside the Pass Rate stat card so the formatted percentage displays in its own card.

  • Save dashboard.jsp.
  • Run the project and navigate to http://localhost:8080/GradeDashboard/dashboard. You should see a dark header with the university name and formatted date, three white stat cards in a row, and a styled table with colour-coded grades and course badges.
  • Try adding a URL parameter: http://localhost:8080/GradeDashboard/dashboard?highlight=Alice. A yellow highlight banner should appear above the table.

Page looks unstyled or broken?

Make sure you kept the three taglib directives at the very top of the file (the page, c, and fmt directives). If you accidentally deleted them during the replacement, add them back above <!DOCTYPE html>.

If CSS classes aren't applying, check that the <style> block is inside <head> (not <body>). Also confirm there are no stray characters or unclosed tags from the earlier version.

Help me fix my dashboard styling and layout issues.

✔️ Awesome, I've got everything!

Great. Double check you've saved dashboard.jsp and that the browser shows the fully styled dashboard.

ⓧ I'd like to double check the full code

Here is the complete dashboard.jsp file as it should look at the end of this step:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ 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>Student Grade Dashboard</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
        .header { background: #2c3e50; color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
        .stats { display: flex; gap: 15px; margin-bottom: 20px; }
        .stat-card { background: white; padding: 15px; border-radius: 8px; flex: 1; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        th { background: #34495e; color: white; padding: 12px; text-align: left; }
        td { padding: 12px; border-bottom: 1px solid #eee; }
        tr:hover { background: #f8f9fa; }
        .grade-high { color: #27ae60; font-weight: bold; }
        .grade-mid { color: #f39c12; font-weight: bold; }
        .grade-low { color: #e74c3c; font-weight: bold; }
        .course-tag { display: inline-block; background: #3498db; color: white; padding: 2px 8px; border-radius: 12px; font-size: 0.8em; margin: 2px; }
        .footer { margin-top: 20px; color: #666; font-size: 0.9em; }
        .highlight-banner { background: #fff3cd; padding: 10px; border-radius: 4px; margin-bottom: 15px; }
    </style>
</head>
<body>

<div class="header">
    <h1>${sessionScope.university} - Grade Dashboard</h1>
    <p>${sessionScope.semester} | ${courseName}</p>
    <p>Generated: <fmt:formatDate value="${currentDate}" pattern="EEEE, dd MMMM yyyy 'at' HH:mm" /></p>
</div>

<div class="stats">
    <div class="stat-card">
        <h3>Total Students</h3>
        <p>${totalStudents}</p>
    </div>
    <div class="stat-card">
        <h3>Pass Rate</h3>
        <c:set var="passCount" value="0" />
        <c:forEach var="s" items="${students}">
            <c:if test="${s.grade >= 50.0}">
                <c:set var="passCount" value="${passCount + 1}" />
            </c:if>
        </c:forEach>
        <p><fmt:formatNumber value="${passCount / totalStudents * 100}" maxFractionDigits="1" />%</p>
    </div>
    <div class="stat-card">
        <h3>Current Page</h3>
        <p>${pageContext.request.requestURI}</p>
    </div>
</div>

<c:if test="${not empty param.highlight}">
    <div class="highlight-banner">
        Highlighting student: <c:out value="${param.highlight}" />
    </div>
</c:if>

<table>
    <thead>
        <tr>
            <th>#</th>
            <th>Name</th>
            <th>Email</th>
            <th>Grade</th>
            <th>Status</th>
            <th>Enrolled</th>
            <th>Courses</th>
        </tr>
    </thead>
    <tbody>
        <c:forEach var="student" items="${students}" varStatus="status">
            <tr>
                <td>${status.count}</td>
                <td><c:out value="${student.name}" /></td>
                <td>${student.email}</td>
                <td>
                    <c:choose>
                        <c:when test="${student.grade >= 80}">
                            <span class="grade-high">
                                <fmt:formatNumber value="${student.grade}" pattern="#0.0" />%
                            </span>
                        </c:when>
                        <c:when test="${student.grade >= 50}">
                            <span class="grade-mid">
                                <fmt:formatNumber value="${student.grade}" pattern="#0.0" />%
                            </span>
                        </c:when>
                        <c:otherwise>
                            <span class="grade-low">
                                <fmt:formatNumber value="${student.grade}" pattern="#0.0" />%
                            </span>
                        </c:otherwise>
                    </c:choose>
                </td>
                <td>
                    <c:if test="${student.grade >= 50}">Pass</c:if>
                    <c:if test="${student.grade < 50}">Fail</c:if>
                </td>
                <td>
                    <fmt:formatDate value="${student.enrollmentDate}" pattern="dd MMM yyyy" />
                </td>
                <td>
                    <c:forEach var="course" items="${student.courses}">
                        <span class="course-tag">${course}</span>
                    </c:forEach>
                </td>
            </tr>
        </c:forEach>
    </tbody>
</table>

<div class="footer">
    <p>Server: ${pageContext.request.serverName}:${pageContext.request.serverPort} |
       Context: ${pageContext.request.contextPath} |
       Protocol: ${pageContext.request.protocol}</p>
    <p>
        <c:choose>
            <c:when test="${totalStudents > 3}">
                Large class (${totalStudents} students)
            </c:when>
            <c:otherwise>
                Small class (${totalStudents} students)
            </c:otherwise>
        </c:choose>
    </p>
</div>

</body>
</html>

Your dashboard now looks like a professional application with clean numbers, readable dates, and polished styling. Next up, you'll take on the Secret Mission and add interactive search and filtering using the JSTL Functions library.

Secret mission

Add Search and Filtering with JSTL Functions

Your dashboard looks great, but what if a lecturer has 200 students? They need to filter and search. In this mission, you'll add interactive filtering using the JSTL Functions library, combining fn:containsIgnoreCase, fn:length, and fn:toUpperCase to build a working search feature without touching the servlet.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.

Resources you used:

  • GradeDashboard NetBeans project (includes Student.java, StudentServlet.java, dashboard.jsp, and JSTL JAR files).
  • Apache Tomcat 9.x server instance running locally.

Keep everything running

No action needed. Choose this if you're still actively building or experimenting with the dashboard.

  • Your GradeDashboard project stays in NetBeans, ready to open and run anytime.
  • The Tomcat server will continue running until you stop it or close NetBeans.
  • The JSTL JAR files remain in your project's libraries.

Pause - I'll come back to this later

Shut down the running Tomcat server to free up memory and the port, but keep your project files intact.

  • In NetBeans, click the red square stop button in the Output panel to stop the Tomcat server.
  • Close NetBeans when you are done. Your project will still be there the next time you open the IDE.

Delete - I don't want to use this again

Remove the project from NetBeans and delete the files from disk.

  • In NetBeans, stop the Tomcat server by clicking the red square stop button in the Output panel.
  • Right-click the GradeDashboard project in the Projects panel and select Delete.
  • When prompted, check the box to Also delete sources to remove the project files from disk.
  • If you downloaded the JSTL JAR files to a separate location outside the project, delete those files manually using Finder (macOS) or File Explorer (Windows).

That's a Wrap!

That's a Wrap!

Nice work! You just built a fully functional Student Grade Dashboard using nothing but EL expressions and JSTL tags.

Your JSP contains zero scriptlet code. Every piece of logic, iteration, and formatting is handled by the tag libraries designed for exactly this purpose.

You've learned how to:

  • Build a complete dashboard powered entirely by EL expressions and JSTL tags, replacing all scriptlet code with clean, maintainable markup.
  • Use EL dot notation, bracket notation, operators, and implicit objects to access data from request, session, and page scopes.
  • Implement conditional rendering with c:choose, iterate collections with c:forEach, and format output with fmt:formatNumber and fmt:formatDate.
  • Secret Mission: Added interactive search and filtering using fn:containsIgnoreCase, fn:length, and fn:toUpperCase from the JSTL Functions library.

Ready to quiz yourself?