Setting Up a Java Web Project
Setting Up a Java Web Project
Before you write a single servlet you need to understand the standardised on-disk layout that every Jakarta EE web container expects, how to bundle your application into a WAR file, and how to get that WAR running inside Apache Tomcat. This lesson is entirely practical: by the end you will have a project structure you can reuse for every servlet you write in this tutorial.
The Standard Web Application Directory Layout
A Jakarta EE web application follows a precise directory contract defined by the Servlet specification. Every compliant container — Tomcat, Jetty, WildFly — honours the same layout:
The most important rule to memorise: everything under WEB-INF/ is invisible to the browser. A request for /WEB-INF/web.xml returns a 404 from the container, not the file. This is how JSPs that must not be accessed directly, sensitive config, and compiled classes are kept safe. Anything placed outside WEB-INF/ — HTML, images, CSS — is publicly accessible.
web.xml. Use environment variables or a secrets manager for credentials, exactly as you would in any other server-side application.
The Deployment Descriptor: web.xml
The file WEB-INF/web.xml is the deployment descriptor. It is an XML file that tells the container about your application: which servlets exist, which URL patterns map to them, session timeout, welcome files, error pages, and security constraints. Since Servlet 3.0, annotations replace most of its content for simple cases, but web.xml remains important for settings that annotations cannot express and for environments where annotation scanning is disabled.
A minimal but complete web.xml for Jakarta EE 10 (Servlet 6.0) looks like this:
No servlet mappings are declared here because the next lesson introduces the @WebServlet annotation, which is the modern way to bind a URL to a servlet class without touching web.xml.
web.xml lets you override annotation-based settings per-environment (e.g., a different session timeout in production) without recompiling. It also serves as explicit documentation of the application's contract.
Setting Up the Maven Build (pom.xml)
Maven's war packaging type knows about the web application layout and produces a correctly structured WAR automatically. Here is a minimal pom.xml that targets Jakarta EE 10 / Tomcat 10.1:
The provided scope on the servlet API dependency is critical. It means Maven compiles against the API but does not bundle it into the WAR. Tomcat already ships the API; if you bundled it too you would get classloader conflicts and hard-to-debug ClassCastException errors at runtime.
jakarta.*, not javax.*. If your imports still read import javax.servlet.* you are targeting the older Java EE namespace and your code will not deploy on Tomcat 10 or any Jakarta EE 10 container. The rename happened with the Jakarta EE 9 specification in 2020. Always check your Tomcat version against the servlet-api version you depend on.
Building the WAR
A WAR (Web Application Archive) is simply a ZIP file with a .war extension and the directory layout described above baked in. To build it:
Maven compiles your Java sources, copies them to WEB-INF/classes/, copies dependency JARs (those not scoped provided) to WEB-INF/lib/, includes everything under src/main/webapp/, and produces target/my-webapp.war. You can verify the contents:
Expected output includes entries like WEB-INF/web.xml, WEB-INF/classes/com/example/web/HelloServlet.class, and any static assets.
Deploying to Tomcat
Tomcat 10.1 ships with a webapps/ directory. Drop a WAR there and Tomcat auto-deploys it:
Tomcat extracts the WAR into a matching directory (e.g., webapps/my-webapp/) and the application becomes available at http://localhost:8080/my-webapp/. The context path (/my-webapp) is derived from the WAR filename. To deploy to the root context, rename the WAR to ROOT.war.
For development, the Tomcat Maven Plugin lets you hot-deploy without manual copying:
This starts an embedded Tomcat instance, deploys your application, and keeps it running. You get a fast edit-compile-refresh cycle without touching a standalone Tomcat installation.
Checking the Logs
Tomcat writes startup and deployment output to $CATALINA_HOME/logs/catalina.out. If your WAR fails to deploy — missing web.xml namespace, a bad servlet mapping, a classloader conflict — the error message is here. Reading this log is the first step whenever a deployment goes wrong.
Summary
You now have a reusable mental model: static content lives under the webapp root; everything sensitive lives under WEB-INF/; web.xml describes the application contract; provided-scoped servlet API avoids classloader conflicts; mvn clean package produces the WAR; dropping it in webapps/ deploys it. In the next lesson you will write the first real servlet and map it to a URL.