Your First Servlet Application

Introduction

This chapter builds a minimal Jakarta Servlet app with Maven, packages it as a WAR, deploys it to standalone Tomcat, and verifies http://localhost:8080/hello/. You will use @WebServlet (no heavy web.xml), provided scope for the Servlet API, and the same directory conventions as Your First Maven Project—extended with src/main/webapp for web assets.

Prerequisites

Goal

text
Maven project  →  mvn package  →  target/hello.war  →  webapps/  →  curl /hello/api
StepOutput
Buildtarget/hello.war
DeployContext path /hello
Servlet URLGET http://localhost:8080/hello/api → plain text response
Static pageGET http://localhost:8080/hello/index.html

Create the Maven WAR Project

From your projects folder:

bash
mkdir -p hello/src/main/java/com/example
mkdir -p hello/src/main/webapp/WEB-INF
cd hello

Create pom.xml at the project root:

Code explanation:

  • <packaging>war</packaging> — Maven produces a WAR, not a JAR
  • jakarta.servlet-api with provided — compile against the API; Tomcat supplies implementation JARs at runtime
  • <finalName>hello</finalName> — output target/hello.war (matches context path /hello)

Warning

Use jakarta, Not javax

Tomcat 10 requires jakarta.servlet.*. javax.servlet is for Tomcat 9 and older stacks.

Directory Layout vs JAR Project

Compare to a typical Maven JAR:

text
hello/                          my-first-app/ (JAR)
├── pom.xml                     ├── pom.xml
└── src/                        └── src/
    └── main/                       └── main/
        ├── java/                       ├── java/          ← same: Java sources
        │   └── com/example/            │   └── com/example/
        │       └── HelloServlet.java     │       └── App.java
        └── webapp/                     └── resources/     ← JAR: config only
            ├── index.html
            └── WEB-INF/
                └── web.xml             (optional)
PathRole
src/main/javaServlets, filters, plain Java classes
src/main/webappDocument root of the WAR—becomes public URLs under context path
src/main/webapp/WEB-INFPrivate web metadata and non-public resources
src/main/resourcesOptional classpath resources (.properties in WEB-INF/classes after build)

Maven mvn package compiles Java into WEB-INF/classes/ inside the WAR and copies webapp/ contents to the WAR root.

Write HelloServlet

src/main/java/com/example/HelloServlet.java:

Code explanation:

  • @WebServlet("/api") — path inside the app; full URL is /hello/api
  • doGet — handles HTTP GET; use doPost for POST
  • HttpServlet — Tomcat calls service(), which dispatches by HTTP method

Add a Static Welcome Page

src/main/webapp/index.html:

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Hello WAR</title>
</head>
<body>
  <h1>hello_code Tomcat WAR</h1>
  <p>Try the API: <a href="api">/hello/api</a></p>
</body>
</html>

Optional minimal src/main/webapp/WEB-INF/web.xml (welcome file only):

xml
<?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">
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
</web-app>

Code explanation:

  • index.html at WAR root → http://localhost:8080/hello/ or /hello/index.html
  • Servlet API 3+ finds @WebServlet even without web.xml—the welcome file XML is optional clarity

Build the WAR

bash
cd hello
mvn clean package

Expected: BUILD SUCCESS and target/hello.war.

Inspect contents:

bash
jar tf target/hello.war

You should see:

text
index.html
WEB-INF/web.xml
WEB-INF/classes/com/example/HelloServlet.class
WEB-INF/lib/          ← empty unless you add dependencies
META-INF/MANIFEST.MF

Deploy to Tomcat

Stop Tomcat if replacing an older deploy:

bash
export CATALINA_HOME=/opt/tomcat   # your path
$CATALINA_HOME/bin/shutdown.sh

Copy WAR:

bash
cp target/hello.war $CATALINA_HOME/webapps/
$CATALINA_HOME/bin/startup.sh

Wait for deploy (watch logs):

bash
tail -f $CATALINA_HOME/logs/catalina.$(date +%Y-%m-%d).log

Look for a line indicating deployment of context /hello.

Verify

bash
curl http://127.0.0.1:8080/hello/api
curl -I http://127.0.0.1:8080/hello/

Expected:

text
Hello from Tomcat Servlet
HTTP/1.1 200

Browser:

text
http://127.0.0.1:8080/hello/
http://127.0.0.1:8080/hello/api

Tip

404 on /api but Not /hello/api

Servlet mapping is relative to the context. Use /hello/api, not /api, when context path is /hello.

Redeploy After Code Changes

Development loop:

bash
# 1. Edit HelloServlet.java
mvn clean package
$CATALINA_HOME/bin/shutdown.sh
rm -rf $CATALINA_HOME/webapps/hello $CATALINA_HOME/webapps/hello.war
cp target/hello.war $CATALINA_HOME/webapps/
$CATALINA_HOME/bin/startup.sh
curl http://127.0.0.1:8080/hello/api

For faster iteration, use catalina.sh run in one terminal and Manager upload—or later Spring Boot embedded server. Standalone Tomcat favors rebuild + redeploy for correctness.

Run from IntelliJ IDEA (Optional)

  1. Open the pom.xml as a Maven project
  2. Confirm Project SDK is JDK 17+
  3. Enable Maven auto-import
  4. RunEdit Configurations+Tomcat ServerLocal (Ultimate) or deploy WAR manually on Community

Community Edition: build with mvn package, copy WAR to webapps/—no bundled Tomcat runner required.

Common Build and Deploy Errors

ErrorCauseFix
package javax.servlet does not existWrong APIUse jakarta.servlet for Tomcat 10
BUILD FAILURE compilerJDK too oldmaven.compiler.release 17+
404 on /hello/apiDeploy not finished; wrong URLCheck logs; use full path with context
500 on first hitServlet exceptionRead logs/localhost.*.log stack trace
WAR deploys as /hello-1.0-SNAPSHOTMissing finalNameSet <finalName>hello</finalName> or rename WAR
ClassNotFoundException for servletWAR missing classesmvn clean package; verify WEB-INF/classes in WAR

How This Differs from Spring Boot

TopicThis chapter (Servlet + Tomcat)Spring Boot
Entry@WebServlet, web.xml@RestController, @SpringBootApplication
Run locallyDeploy WAR to Tomcatmvn spring-boot:run
Servlet APIYou call HttpServletResponseSpring MVC abstracts request/response
Configweb.xml, context.xmlapplication.yml

Spring Boot still uses Tomcat embedded by default—you are learning the container-native path that WAR deploy and legacy apps use.

Post-Chapter Checklist

  • packaging is war with jakarta.servlet-api provided
  • mvn package produces target/hello.war
  • WAR deployed; curl returns greeting from /hello/api
  • index.html served at /hello/
  • You can explain URL = context path + servlet path

What Comes Next

ChapterTopic
09 — Servlet, JSP, and Filter basicsLifecycle, POST, sessions, filters
16 — Project: Maven WAR deployExtended exercise
14 — Spring Boot WAR on TomcatBoot app on external Tomcat

FAQ

Can I use mvn archetype for WAR?

Yes:

bash
mvn archetype:generate \
  -DgroupId=com.example \
  -DartifactId=hello \
  -DarchetypeArtifactId=maven-archetype-webapp \
  -DarchetypeVersion=1.5 \
  -DinteractiveMode=false

Then replace generated index.jsp with this chapter’s Servlet and add jakarta.servlet-api provided.

Why provided scope for servlet-api?

Tomcat already includes the Servlet implementation. Bundling it in WEB-INF/lib can cause class loader conflicts.

Deploy as ROOT (no /hello prefix)?

Rename artifact to ROOT.war or use conf/Catalina/localhost/ROOT.xml—see Deploying WAR.

Test without Tomcat?

Use embedded runners (IDE Tomcat, Spring Boot) or maven-surefire with HttpServlet unit tests and mocks—out of scope here; production path is Tomcat deploy.

Java version 17 vs 21?

Both work on Tomcat 10.1 with matching maven.compiler.release. Match your team JDK.

Where is web.xml generated?

Only if you create it under src/main/webapp/WEB-INF/. Annotations-only apps may omit it entirely.