Servlets + JSP
STEP 1 · BIG PICTURE

Build one complete Java web flow

We will create the Maven web project from VS Code, deploy the generated JSP first, add a Servlet that forwards to another JSP, and finally build a student form handled by a Servlet with a separate result page.

By the end: You will understand how VS Code, Maven, WAR packaging, Tomcat, Servlets, JSP, request parameters, request attributes, and forwarding fit together.
JSP

Displays pages and HTML forms.

→
Servlet

Handles requests and prepares data.

→
JSP

Displays the result prepared by the Servlet.

Target stack

Java 21 · Maven · Tomcat 11 · Jakarta Servlet 6.1 · VS Code · Tomcat extension by Al-rimi

STEP 2 · PREREQUISITES

Verify Java, Maven, VS Code, and Tomcat

The commands are the same on Windows, macOS, and Linux once Java and Maven are installed and available on PATH.

java --version
javac --version
mvn -version
Java 21

java and javac should report Java 21.

Maven

mvn -version should also report that Maven is running with your Java 21 JDK.

TOMCAT

Use a normal extracted Tomcat installation whose root contains bin, conf, lib, and webapps. Tomcat 11 requires Java 17 or newer, so Java 21 is suitable.

STEP 3 · COMMAND PALETTE

Create the web app from VS Code

Do not create the folder structure manually. Use VS Code's Maven command so students learn the normal IDE workflow.

  1. Open VS Code.
  2. Open the Command Palette with Ctrl + Shift + P on Windows/Linux or Cmd + Shift + P on macOS.
  3. Run Maven: Create Maven Project.
  4. Select maven-archetype-webapp.
  5. Choose the archetype version offered by VS Code/Maven.
  6. Enter Group ID: com.example.
  7. Enter Artifact ID: servlet-demo.
  8. Choose the folder where you want the project created, then open that project in VS Code.
servlet-demo/ ├── pom.xml └── src/ └── main/ └── webapp/ ├── index.jsp └── WEB-INF/ └── web.xml
Important:

The webapp archetype already creates an index.jsp. We are going to deploy and test that generated page before creating any Servlet.

STEP 4 · MAVEN CONFIGURATION

Configure Java 21 and WAR packaging

The generated project may contain older defaults. Replace or update the relevant settings so the application compiles with Java 21.

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
  http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>
  <artifactId>servlet-demo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>21</maven.compiler.source>
    <maven.compiler.target>21</maven.compiler.target>
  </properties>

  <dependencies>
    <!-- We add the Servlet dependency in the next step. -->
  </dependencies>

  <build>
    <finalName>servlet-demo</finalName>
  </build>

</project>
war

Creates a deployable Java web application.

source / target 21

Prevents old Java 7 compiler defaults from breaking the build.

finalName

Produces servlet-demo.war and gives us a predictable context path.

NOTE

You do not need failOnMissingWebXml in this codelab. Keep the generated WEB-INF/web.xml in place.

STEP 5 · MVNREPOSITORY

Find dependencies instead of memorizing XML

Use Maven Repository to search for libraries and copy the Maven dependency snippet into pom.xml.

Open MVNRepository ↗

  1. Open MVNRepository.
  2. Search for jakarta servlet api.
  3. Open jakarta.servlet » jakarta.servlet-api.
  4. Select version 6.1.0 for Tomcat 11.
  5. Choose the Maven snippet and copy it.
  6. Paste it inside the <dependencies> section of pom.xml.
  7. Add <scope>provided</scope> because Tomcat supplies the Servlet API at runtime.
<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>6.1.0</version>
    <scope>provided</scope>
</dependency>
Why provided?

Your code needs the Servlet API to compile, but Tomcat already provides those classes when the application runs. We therefore do not need to package another copy inside the WAR.

STEP 6 · VS CODE DEPLOYMENT

Configure Tomcat by Al-rimi

We will let the extension build and deploy the Maven project instead of manually running mvn package and copying the WAR.

  1. Open Extensions in VS Code.
  2. Search for Al-rimi.tomcat or Tomcat by Al-rimi.
  3. Install the extension.
  4. Add/configure your Tomcat installation. Select the Tomcat root folder containing bin, conf, lib, and webapps.
  5. Configure Java Home if requested. Point it to your JDK 21 installation.
  6. Use port 8080 unless you already have another service using it.
  7. Use the Maven build strategy for this Maven project.
Cross-platform paths

On Windows a Tomcat path may look like C:\apache-tomcat-11. On macOS/Linux it may look like ~/Developer/apache-tomcat-11. The exact location is not important; the folder structure is.

STEP 7 · WEB.XML

Update web.xml for Jakarta Servlet

Before deploying the application, update the deployment descriptor generated by the Maven webapp archetype. Older archetypes may generate an old Java EE web.xml, while Tomcat 11 uses Jakarta EE.

Open:

src/main/webapp/WEB-INF/web.xml

Replace its existing contents with:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
  version="6.0">
  <display-name>tomcat_test</display-name>
</web-app>
Why are we changing this?

The Maven webapp archetype can generate an older deployment descriptor that uses the previous Java EE schema. Our application uses jakarta.servlet and runs on Tomcat 11, so we update the descriptor to the Jakarta EE web application schema before deployment.

What is web.xml?

web.xml is the web application's deployment descriptor. It lives inside WEB-INF and can contain application-level web configuration such as Servlet mappings, filters, listeners, welcome files, error pages, and other settings. In this codelab we use annotations such as @WebServlet for Servlet mappings, so the descriptor can remain minimal.

DISPLAY NAME

<display-name> is a human-readable application name. It does not determine the URL context path. The deployed WAR name still determines the normal context path, such as /servlet-demo.

STEP 8 · FIRST DEPLOYMENT

Deploy before writing any Servlet

This separates deployment problems from Servlet problems. First prove that Maven + Tomcat + VS Code are working together.

  1. Make sure servlet-demo is open as your VS Code workspace.
  2. Use the Tomcat extension's deploy command/button for the current project.
  3. The extension should detect the Maven project, build it, and deploy it to Tomcat.
  4. Start Tomcat from the extension if it is not already running.
  5. Open the deployed application in the browser.
http://localhost:8080/servlet-demo
Expected output:

You should see the content from the generated src/main/webapp/index.jsp. This confirms that the project can already build and deploy successfully.

WHY THIS MATTERS

If index.jsp works now but a Servlet fails later, you know Tomcat itself and the basic deployment are not the problem.

STEP 9 · JSP VIEW

Create a JSP for the Servlet to serve

Instead of making the Servlet print HTML with response.getWriter(), we will let a JSP render the page.

Create:

src/main/webapp/hello.jsp
<!DOCTYPE html>
<html>
<head>
    <title>Hello Servlet</title>
</head>
<body>
    <h1>Hello from JSP!</h1>
    <p>This page was opened through a Servlet.</p>
</body>
</html>
STEP 10 · FIRST SERVLET

Forward a request from Java to JSP

Create the Java source folder if the archetype did not generate it, then add the Servlet.

src/ └── main/ ├── java/ │ └── com/ │ └── example/ │ └── HelloWorldServlet.java └── webapp/ ├── index.jsp └── hello.jsp
package com.example;

import java.io.IOException;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet("/hello")
public class HelloWorldServlet extends HttpServlet {

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

        request.getRequestDispatcher("/hello.jsp")
               .forward(request, response);
    }
}

Understand the code

package com.example;
Places the class inside the com.example Java package.
extends HttpServlet
Makes our class a Servlet that can participate in HTTP request handling.
@WebServlet("/hello")
Maps the URL path /hello to this Servlet.
doGet(...)
Runs when the browser sends an HTTP GET request to this Servlet.
HttpServletRequest request
Represents the incoming request. It can contain parameters, headers, attributes, session information, and more.
HttpServletResponse response
Represents the outgoing HTTP response.
getRequestDispatcher("/hello.jsp")
Gets a dispatcher that knows how to hand the current request to hello.jsp.
.forward(request, response)
Forwards the same request and response objects to the JSP. The browser does not make a second request.

What makes /hello execute this Servlet?

STEP 11 · TEST THE SERVLET

Redeploy with the Tomcat extension

Use the same deploy command again. The extension handles Maven build + deployment for you.

  1. Save HelloWorldServlet.java and hello.jsp.
  2. Deploy the project again using the Al-rimi Tomcat extension.
  3. Wait for the deployment to finish successfully.
  4. Open the Servlet URL.
http://localhost:8080/servlet-demo/hello
GET /hello

The browser requests the Servlet.

→
HelloWorldServlet

Tomcat calls doGet().

→
hello.jsp

The Servlet forwards to the JSP.

STEP 12 · HTML FORM

Create the student form JSP

The form will submit a POST request back to a Student Servlet.

Create:

src/main/webapp/student-form.jsp
<!DOCTYPE html>
<html>
<head>
    <title>Student Form</title>
</head>
<body>

    <h1>Student Registration</h1>

    <form action="${pageContext.request.contextPath}/student" method="post">

        <label>Name:</label>
        <input type="text" name="name" required>

        <br><br>

        <label>Email:</label>
        <input type="email" name="email" required>

        <br><br>

        <label>Course:</label>
        <input type="text" name="course" required>

        <br><br>

        <button type="submit">Submit</button>

    </form>

</body>
</html>
Why use the context path?

${pageContext.request.contextPath} produces the application's current context, such as /servlet-demo. The form therefore remains correct even if the application name changes later.

STEP 13 · RESULT VIEW

Create a separate JSP for the result

The Servlet will put values into request attributes, then this JSP will display them using JSP Expression Language.

Create:

src/main/webapp/result.jsp
<!DOCTYPE html>
<html>
<head>
    <title>Student Result</title>
</head>
<body>

    <h1>Student Submitted Successfully</h1>

    <p>Name: ${studentName}</p>
    <p>Email: ${studentEmail}</p>
    <p>Course: ${studentCourse}</p>

    <a href="${pageContext.request.contextPath}/student">
        Submit another student
    </a>

</body>
</html>
Expression Language:

${studentName} asks JSP to find an attribute called studentName. We will create that attribute inside the Servlet.

STEP 14 · GET + POST

Use one Servlet to show and process the form

doGet() displays the form. doPost() processes submitted data and forwards to the result page.

Create:

src/main/java/com/example/StudentServlet.java
package com.example;

import java.io.IOException;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

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

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

        request.getRequestDispatcher("/student-form.jsp")
               .forward(request, response);
    }

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

        String name = request.getParameter("name");
        String email = request.getParameter("email");
        String course = request.getParameter("course");

        request.setAttribute("studentName", name);
        request.setAttribute("studentEmail", email);
        request.setAttribute("studentCourse", course);

        request.getRequestDispatcher("/result.jsp")
               .forward(request, response);
    }
}

What changed?

getParameter()

Reads values submitted by HTML inputs. The argument must match the input's name.

setAttribute()

Attaches Java-side data to the current request so the next JSP can display it.

MethodPurpose in our app
GET /studentShow student-form.jsp.
POST /studentRead the submitted form and show result.jsp.
STEP 15 · COMPLETE FLOW

Deploy and test the student workflow

The final application now demonstrates both a simple Servlet-to-JSP forward and a complete form-processing flow.

  1. Save all Java and JSP files.
  2. Deploy the project again using the Al-rimi Tomcat extension.
  3. Open http://localhost:8080/servlet-demo/student.
  4. Tomcat executes StudentServlet.doGet().
  5. The Servlet forwards to student-form.jsp.
  6. Fill in Name, Email, and Course.
  7. Click Submit.
  8. The browser sends POST /servlet-demo/student.
  9. Tomcat executes StudentServlet.doPost().
  10. The Servlet reads the parameters, creates request attributes, and forwards to result.jsp.
http://localhost:8080/servlet-demo/student
StudentServlet.doGet()

Forwards to the form.

→
student-form.jsp

Submits POST data.

→
StudentServlet.doPost()

Reads data → forwards to result.jsp.

Optional challenge

  1. Add an age field to the form.
  2. Read it using request.getParameter("age").
  3. Create a studentAge request attribute.
  4. Display it on result.jsp.

Completion checklist

  • I created the Maven webapp through the VS Code Command Palette.
  • I configured Java 21 and WAR packaging in pom.xml.
  • I found and added Jakarta Servlet API from MVNRepository.
  • I configured the Tomcat extension by Al-rimi.
  • I deployed and tested the generated index.jsp before creating a Servlet.
  • I understand @WebServlet, HttpServlet, doGet(), request, and response.
  • I forwarded from HelloWorldServlet to hello.jsp.
  • I built student-form.jsp, StudentServlet, and result.jsp.
  • I used getParameter() for form values.
  • I used setAttribute() to pass data from a Servlet to JSP.

Codelab complete! 🎉

You can now create, deploy, and trace a traditional Java web application using Maven, Tomcat, Servlets, and JSP.