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.
- 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 viewEverything 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); }}`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
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.