Back to Blog

How I Created My JSP Servlet Basics Project (CRUD with Embedded Tomcat)

How I Created My JSP Servlet Basics Project (CRUD with Embedded Tomcat)

I wanted a minimal project to really understand the classic Java web flow end-to-end: JSP for views, Servlets for request handling, JDBC for persistence, and a lightweight local runtime.

So I built this project: JspServletBasics.

What I wanted from this project

My goals were simple:

  • Keep architecture easy to reason about
  • Avoid framework magic while learning fundamentals
  • Run locally with as little setup as possible
  • Implement full CRUD with validation

That led me to this stack:

  • Java 17
  • Jakarta Servlet + JSP + JSTL
  • Embedded Tomcat 10
  • H2 database (file-backed)
  • Maven

The project structure I used

src/main/java/com/example/jspservletbasics/
  Main.java
  dao/ProductDao.java
  db/Database.java
  model/Product.java
  web/HomeServlet.java
  web/ProductServlet.java

src/main/resources/
  schema.sql

src/main/webapp/
  assets/styles.css
  WEB-INF/web.xml
  WEB-INF/views/products.jsp
  WEB-INF/views/product-form.jsp

I separated responsibilities so each layer stays focused:

  • web/*Servlet handles routing, request parsing, and response flow
  • dao/ProductDao talks to DB with JDBC
  • db/Database owns schema init + seed data
  • model/Product is the domain object
  • JSP files handle rendering only

How I bootstrapped the app without external Tomcat

Instead of deploying a WAR into an installed Tomcat, I created a Main class that starts embedded Tomcat.

In Main.java, I:

  • Read port from app.port system property or PORT env var
  • Point Tomcat to src/main/webapp
  • Attach compiled classes from target/classes to /WEB-INF/classes
  • Start server and block with await()

This gave me a runnable local app with:

mvn compile exec:java

Default URL: http://localhost:8080

Custom port:

mvn compile exec:java -Dapp.port=9090

Servlet mapping and route design

I kept mappings in WEB-INF/web.xml:

  • HomeServlet -> /
  • ProductServlet -> /products/*

HomeServlet just redirects to /products.

Inside ProductServlet, I used path-based switching:

  • GET /products -> list
  • GET /products/new -> create form
  • GET /products/edit?id=... -> edit form
  • POST /products/create -> insert
  • POST /products/update -> update
  • POST /products/delete -> delete

That one servlet became the CRUD coordinator while keeping URL structure clear.

Form handling and validation choices

I added a small ProductFormResult record to return:

  • parsed product
  • validation status
  • error message

Validation rules I enforced:

  • product name is required
  • price is required
  • price must be a valid number
  • price cannot be negative
  • id is required for update

When validation fails, I forward back to product-form.jsp with:

  • errorMessage
  • previously entered values (product)
  • correct form action and title

When validation passes, I write to DB and redirect back to list with a message query parameter.

JDBC and database setup

I used H2 with a file-backed JDBC URL under the project data/ directory.

Database.initialize() does three things:

  1. Creates the data directory
  2. Executes schema.sql
  3. Seeds sample rows only if table is empty

ProductDao uses plain PreparedStatement for:

  • findAll
  • findById
  • create
  • update
  • deleteById

This gave me SQL control and safe parameter binding without adding ORM complexity.

JSP layer and why WEB-INF views helped

I placed views inside WEB-INF/views so users cannot access them directly by URL.

Only servlets can forward to:

  • products.jsp for listing/table/actions
  • product-form.jsp for create/edit

In JSP, I used JSTL tags (c:if, c:choose, c:forEach) to keep templates readable and avoid scriptlets.

Problems I avoided by design

A few small decisions made the project smoother:

  • Redirect-after-post to avoid duplicate submissions on refresh
  • Centralized DB init in servlet init()
  • Explicit path normalization (null/blank -> /)
  • Reusable parseId and trim helpers

Final takeaway

This project gave me exactly what I wanted: a practical understanding of how classic Java web apps work under the hood.

The most useful learning was seeing the full lifecycle clearly:

Request -> Servlet -> DAO -> DB -> Servlet -> JSP -> Response

If you are learning Java web fundamentals, building one CRUD app this way is worth it before jumping to bigger frameworks.

Repository

GitHub: https://github.com/arkomandal/jsp-servlet-basics