LearnAI ToolsCareerPractice BuildsPlayContact
JSPBeginner~1.5 hours

Student Registration System

Build a form-driven app using Servlets as controllers and JSP as views.

MVC PatternFormsJavaBeans

Overview

A student registration system is the classic first JSP project because it forces you to draw a line that a lot of beginner code blurs: the line between "code that decides what happened" and "code that renders what happened." In the Model 2 (MVC) pattern that virtually every real JSP application follows, a `Servlet` is the controller — it reads request parameters, talks to the model, and decides which view to show — while a `.jsp` file is the view, whose only job is to render data it is handed, never to make decisions about it.

By the end of this tutorial you will have a working registration flow: an HTML form posts to a `RegisterServlet`, the servlet reads the submitted fields with `request.getParameter()`, populates a `Student` JavaBean, stores that bean in request scope, and forwards the request to `confirmation.jsp`, which renders the bean's fields using `<jsp:useBean>` and `<jsp:getProperty>` without a single line of Java visible in the page itself. You will also see exactly why a JavaBean has to follow a strict shape — a public no-arg constructor plus `getX`/`setX` pairs — before JSP's bean tags will work with it at all.

What You'll Build
  • A `Student` JavaBean with private fields, a public no-arg constructor, and getter/setter pairs.
  • A plain HTML registration form (`register.html`) that POSTs to the servlet.
  • A `RegisterServlet` that reads form parameters, populates the bean, and forwards to a JSP view.
  • A `confirmation.jsp` page that renders the bean with `<jsp:useBean>` and `<jsp:getProperty>`.
  • A `web.xml` deployment descriptor mapping a URL pattern to the servlet.

Prerequisites

  • Core Java — classes, private fields, constructors, getters and setters.
  • HTML forms — `<form>`, `method="post"`, `<input name="...">`, and how form fields map to submitted parameters.
  • Servlet basics — what `HttpServlet`, `doGet()`, and `doPost()` are for.
  • A servlet container such as Apache Tomcat to deploy and run the finished `.war`.
  • The idea of "request scope" — data attached to one HTTP request/response cycle and nothing longer.

Project Structure

This project follows the classic Model 2 (MVC) layout for a JSP web app packaged as a WAR:

StudentRegistrationApp/
└── src/main/webapp/
├── register.html // The View (input side): plain HTML form, no server code
├── confirmation.jsp // The View (output side): renders the Student bean, no business logic
└── WEB-INF/
├── web.xml // Deployment descriptor: maps a URL pattern to RegisterServlet
└── classes/
├── Student.java // The Model: a plain JavaBean holding one student's data
└── RegisterServlet.java // The Controller: reads the form, fills the bean, forwards to the view

Everything under `WEB-INF/` is invisible to a direct browser request — a client can never navigate straight to `RegisterServlet.class` or `Student.class`, only ever reach them indirectly through a URL the servlet container maps in `web.xml` (Step 5) or through a `@WebServlet` annotation. `register.html` and `confirmation.jsp`, by contrast, sit directly under `webapp/` where the container serves them straight to the browser.

Step 1: Define the Student JavaBean

A JavaBean is not a special Java construct — it is a plain class that follows a strict naming convention: every field is `private`, there is a `public` no-argument constructor, and every field is exposed through a `public` getter (`getX()`) and setter (`setX(...)`) pair named to exactly match the field. JSP's `<jsp:useBean>` and `<jsp:setProperty>` tags (used in Step 4) rely on Java reflection to find these exact method names at runtime — if the no-arg constructor were missing, `<jsp:useBean>` could not instantiate the class, and if a setter were misnamed, `<jsp:setProperty>` would silently fail to find it.

package beans;
import java.io.Serializable;
// A JavaBean: private fields + public no-arg constructor + public getX/setX
// pairs. This exact shape is what lets <jsp:useBean> instantiate it via
// reflection and <jsp:setProperty>/<jsp:getProperty> read and write its
// fields by name, with zero Java code visible inside the JSP page itself.
public class Student implements Serializable { // Serializable is conventional for beans that may cross scopes/sessions
private String name; // Full name entered on the registration form
private String email; // Contact email entered on the registration form
private String course; // Course the student is registering for
private int age; // Age entered on the registration form
public Student() {
// Required no-arg constructor: <jsp:useBean> calls this via
// reflection when it creates the bean, so it must exist and be public.
}
public String getName() {
return name;
}
public void setName(String name) { // Name must be exactly "setName" to match property "name"
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

Step 2: Build the Registration Form

`register.html` is plain, static HTML with no server-side code at all — it is the input half of the view layer. Every `<input>`'s `name` attribute becomes a request parameter key the servlet will read in Step 3, so these names must match `request.getParameter("...")` calls exactly. The form's `method="post"` matters too: registration creates data, and POST (unlike GET) does not appear in browser history or get resubmitted silently on page refresh.

<!DOCTYPE html>
<html>
<head>
<title>Student Registration</title>
</head>
<body>
<h1>Student Registration</h1>
<!-- action points at the URL pattern web.xml maps to RegisterServlet in Step 5 -->
<form action="register" method="post">
<label>Name: <input type="text" name="name" required></label><br>
<label>Email: <input type="email" name="email" required></label><br>
<label>Course: <input type="text" name="course" required></label><br>
<label>Age: <input type="number" name="age" required></label><br>
<!-- No name attribute needed on the submit button; it is not read as a parameter -->
<button type="submit">Register</button>
</form>
</body>
</html>

Step 3: Write the RegisterServlet

The servlet is the controller: it never writes HTML directly (that would blur the exact line this project is built to teach), it only reads input, builds the model, and hands off to a view. `doPost()` overrides `HttpServlet`'s handler for POST requests, matching `register.html`'s `method="post"`. Every field is read with `request.getParameter("name")`, where the string must match the form input's `name` attribute from Step 2 exactly, including case.

package controllers;
import beans.Student;
import java.io.IOException;
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 registers this class at /register without needing a matching
// <servlet-mapping> in web.xml, but Step 5 still shows the XML equivalent
// since some containers/tutorials expect the descriptor form.
@WebServlet("/register")
public class RegisterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Read every submitted field by the exact name used in the form's
// input "name" attributes. getParameter() always returns a String,
// even for the numeric "age" field, so it must be parsed explicitly.
String name = request.getParameter("name");
String email = request.getParameter("email");
String course = request.getParameter("course");
int age = Integer.parseInt(request.getParameter("age")); // Form data is always text on the wire
// Populate the Model: fill a fresh Student bean with the submitted data.
// This is the controller's real job -- turning raw request parameters
// into a well-typed object the view can render without guesswork.
Student student = new Student();
student.setName(name);
student.setEmail(email);
student.setCourse(course);
student.setAge(age);
// Attach the bean to request scope under the name confirmation.jsp's
// <jsp:useBean id="student" scope="request" .../> tag will look for.
// Request scope means this data lives only as long as this one
// request/response cycle -- it disappears the instant this exchange ends.
request.setAttribute("student", student);
// forward() hands control to the view WITHOUT the browser seeing a
// redirect: the URL bar still shows /register, but confirmation.jsp
// now renders using the same request (and the attribute just set on it).
RequestDispatcher dispatcher = request.getRequestDispatcher("/confirmation.jsp");
dispatcher.forward(request, response);
}
}
forward() vs. sendRedirect()

`RequestDispatcher.forward()` reuses the same request/response pair server-side, so `request.setAttribute()` data survives the hop and the browser never sees a second round trip. `response.sendRedirect()` instead tells the browser to make a brand-new GET request to a different URL, which would lose the `student` attribute entirely unless it were re-added some other way (like a session or query string). Model 2 forwards from a controller to its view almost exclusively for this reason.

Step 4: Display the Result in confirmation.jsp

`confirmation.jsp` never touches `request.getParameter()` — that already happened in the servlet. Instead it uses `<jsp:useBean>` to locate the `student` attribute the servlet placed in request scope, then `<jsp:getProperty>` to print each field by calling the matching `getX()` method via reflection. Because `class="beans.Student"` matches the JavaBean built in Step 1 exactly, and because that bean has a public no-arg constructor, `<jsp:useBean>` can resolve it without a single visible line of Java in this file.

<!DOCTYPE html>
<html>
<head>
<title>Registration Confirmed</title>
</head>
<body>
<h1>Registration Confirmed</h1>
<!--
useBean looks up an existing "student" attribute in request scope
(the one RegisterServlet set with request.setAttribute("student", ...)).
scope="request" must match the scope the servlet actually used, or
useBean would create a brand-new, empty Student instead of finding it.
-->
<jsp:useBean id="student" class="beans.Student" scope="request" />
<p>Name: <jsp:getProperty name="student" property="name" /></p>
<p>Email: <jsp:getProperty name="student" property="email" /></p>
<p>Course: <jsp:getProperty name="student" property="course" /></p>
<p>Age: <jsp:getProperty name="student" property="age" /></p>
<!-- property="name" resolves to student.getName() by reflection at request time -->
<a href="register.html">Register another student</a>
</body>
</html>

The same data could be printed with Expression Language instead — `${student.name}` is equivalent to `<jsp:getProperty name="student" property="name" />` and is what the Dynamic Report Page project later in this course leans on. Both approaches read the bean through its getters; `<jsp:getProperty>` is the older, more explicit tag-based style, while `${...}` EL is the terser modern convention.

Step 5: Wire Up web.xml

The `@WebServlet("/register")` annotation on `RegisterServlet` in Step 3 is already enough to map the URL on a Servlet 3.0+ container, but many teams and tutorials still configure servlets declaratively in `web.xml` so every mapping is visible in one file. Both approaches produce the exact same routing; a project should pick one style and stay consistent rather than mixing them for the same servlet.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- <servlet>/<servlet-mapping> is the XML-only equivalent of @WebServlet("/register") -->
<servlet>
<servlet-name>RegisterServlet</servlet-name>
<servlet-class>controllers.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RegisterServlet</servlet-name>
<url-pattern>/register</url-pattern> <!-- Must match the form's action="register" from Step 2 -->
</servlet-mapping>
<!-- The page a browser lands on for the bare context root -->
<welcome-file-list>
<welcome-file>register.html</welcome-file>
</welcome-file-list>
</web-app>

Complete Code

Here is every file assembled together, ready to drop into a `src/main/webapp` Maven/Gradle web-app layout (or the equivalent Tomcat `webapps/<app>` folder) and deploy as a WAR.

// ---------- WEB-INF/classes/beans/Student.java ----------
package beans;
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private String email;
private String course;
private int age;
public Student() {
}
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 String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// ---------- WEB-INF/classes/controllers/RegisterServlet.java ----------
package controllers;
import beans.Student;
import java.io.IOException;
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("/register")
public class RegisterServlet extends HttpServlet {
@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");
int age = Integer.parseInt(request.getParameter("age"));
Student student = new Student();
student.setName(name);
student.setEmail(email);
student.setCourse(course);
student.setAge(age);
request.setAttribute("student", student);
RequestDispatcher dispatcher = request.getRequestDispatcher("/confirmation.jsp");
dispatcher.forward(request, response);
}
}
<!-- ---------- webapp/register.html ---------- -->
<!DOCTYPE html>
<html>
<head>
<title>Student Registration</title>
</head>
<body>
<h1>Student Registration</h1>
<form action="register" method="post">
<label>Name: <input type="text" name="name" required></label><br>
<label>Email: <input type="email" name="email" required></label><br>
<label>Course: <input type="text" name="course" required></label><br>
<label>Age: <input type="number" name="age" required></label><br>
<button type="submit">Register</button>
</form>
</body>
</html>
<!-- ---------- webapp/confirmation.jsp ---------- -->
<!DOCTYPE html>
<html>
<head>
<title>Registration Confirmed</title>
</head>
<body>
<h1>Registration Confirmed</h1>
<jsp:useBean id="student" class="beans.Student" scope="request" />
<p>Name: <jsp:getProperty name="student" property="name" /></p>
<p>Email: <jsp:getProperty name="student" property="email" /></p>
<p>Course: <jsp:getProperty name="student" property="course" /></p>
<p>Age: <jsp:getProperty name="student" property="age" /></p>
<a href="register.html">Register another student</a>
</body>
</html>

Sample Run

Request/Response Flow

Click Run to see what this code prints.

Extend This Project

  • Add server-side validation in `RegisterServlet` (e.g. reject a blank name or an age under 16) and forward back to `register.html` with an error message instead of `confirmation.jsp` when validation fails.
  • Replace the in-memory-only bean with a `RegisteredStudents` list in application scope (`ServletContext`) so every registration accumulates into a roster visible across requests.
  • Swap `<jsp:getProperty>` for Expression Language (`${student.name}`) throughout `confirmation.jsp` and compare the two styles side by side.
  • Add a `doGet()` override to `RegisterServlet` that redirects straight to `register.html`, so a stray GET to `/register` does not throw a 405.
  • Persist each `Student` bean to a database with JDBC instead of only holding it in request scope, so registrations survive server restarts.

Summary

You built a complete Model 2 registration flow: an HTML form for input, a `RegisterServlet` controller that turns form parameters into a `Student` JavaBean, and a `confirmation.jsp` view that renders that bean using `<jsp:useBean>`/`<jsp:getProperty>` without any embedded Java. The controller-populates-model, `forward()`-to-view shape you wrote here is the backbone of every larger JSP/Servlet application you will build from here on, including the session-based login flow and the JSTL-driven report page in the next two projects.