Your First Servlet
Your First Servlet
A servlet is a Java class that lives inside a web container (like Tomcat or Jetty), receives HTTP requests, and writes HTTP responses. You already know how to design classes, handle exceptions, and structure Java programs — a servlet is just a specialised class that plugs into the container's infrastructure. This lesson walks through exactly what that means, from the minimal class structure to the annotations that register the servlet with the container.
The HttpServlet Base Class
Every HTTP-handling servlet extends jakarta.servlet.http.HttpServlet. This abstract class is part of the Jakarta Servlet specification (formerly javax.servlet) and provides the framework you override rather than build from scratch. The class hierarchy looks like this:
GenericServlet handles the wiring to the container: it stores the ServletConfig, implements getServletName(), and provides default no-op implementations of the lifecycle methods. HttpServlet adds HTTP-specific dispatch: its service() method inspects the request method (GET, POST, PUT, etc.) and calls the appropriate doXxx() method on your subclass. You never call service() directly — the container does.
javax.servlet to jakarta.servlet. Tomcat 10+ and any Jakarta EE 10/11 server uses the jakarta.* namespace. Older Tomcat 9 / Java EE 8 servers still use javax.*. Always check which version your container targets and import accordingly.
The @WebServlet Annotation
Before annotations existed, you had to declare every servlet in web.xml with verbose XML. Since Servlet 3.0, the @WebServlet annotation on the class itself is the preferred approach — it keeps the URL mapping next to the code that handles it.
Let's unpack the annotation attributes:
name— a logical identifier used internally by the container (and bygetServletName()). Optional; defaults to the fully-qualified class name.urlPatterns— one or more URL patterns this servlet handles. The pattern/hellomatches the pathhttp://host:port/yourApp/helloexactly. You can also use prefix patterns (/api/*) or extension mappings (*.do).- The shorthand
@WebServlet("/hello")(single string) is equivalent tourlPatterns = "/hello"and is the most common form in practice.
@WebServlet is relative to the application's context root, not the server root. If you deploy myapp.war, a pattern of /hello resolves to /myapp/hello. During development with an IDE or Maven plugin you often set the context root to / to keep URLs short.
The doGet Method in Detail
The container calls doGet when it receives an HTTP GET request matching the servlet's URL pattern. The two parameters give you everything you need to handle the request:
HttpServletRequest request— wraps the incoming HTTP message. Gives you access to URL parameters, headers, the request body, cookies, session, locale, and the client's IP address.HttpServletResponse response— wraps the outgoing HTTP message. Lets you set the status code, response headers, and write the response body through either aPrintWriter(text) or aServletOutputStream(binary).
The method signature declares throws IOException. HttpServlet.doGet also declares throws ServletException — you can add that too, but you must import jakarta.servlet.ServletException. A realistic override looks like this:
Notice the three-step pattern: read input → apply logic → write output. Keeping this separation makes the servlet easy to test: business logic belongs in plain Java service classes, not inside the servlet itself. The servlet's only job is translating HTTP into method calls and method results back into HTTP.
Reading the Request Object
The HttpServletRequest interface exposes the full HTTP request. The most commonly used methods in a doGet handler:
request.getParameter("key")— returns the first value of a query-string or form parameter, ornullif absent.request.getParameterValues("key")— returns all values for a multi-valued parameter (e.g. checkboxes) as aString[].request.getHeader("Accept-Language")— returns a named request header.request.getMethod()— returns"GET","POST", etc.request.getRequestURI()— the path portion of the URL, e.g./myapp/hello.request.getAttribute("key")/request.setAttribute("key", value)— request-scoped storage used when forwarding to a JSP or another servlet.
getParameter() returns raw strings from the browser. Always validate, sanitise, and HTML-encode any value you embed in the response. Failing to do so is the root cause of Cross-Site Scripting (XSS) vulnerabilities. At minimum, escape <, >, and & before writing user-supplied strings into HTML output.
Writing the Response Object
Before you write the response body you must call response.setContentType(). This sets the Content-Type HTTP header so the browser knows how to interpret the bytes. Common values:
"text/html;charset=UTF-8"— an HTML page."application/json;charset=UTF-8"— a JSON API response."application/octet-stream"— a binary file download.
You must set the content type before calling getWriter() or getOutputStream(). Once you start writing the body the headers are committed (sent to the client) and can no longer be changed.
For text responses use response.getWriter() which returns a PrintWriter. For binary responses (images, PDFs, ZIP files) use response.getOutputStream() which returns a ServletOutputStream. You can only obtain one of the two per request — calling both throws an IllegalStateException.
A Complete, Runnable Example
Here is a self-contained servlet that greets a visitor by name and demonstrates all the concepts above in a realistic way:
A GET request to /greet?name=Alice produces a 200 OK HTML page saying "Hello, Alice!". A request without the parameter receives a 400 Bad Request error response — handled by the container's default error page.
What the Container Does for You
It is worth being explicit about the work the container (Tomcat, Jetty, WildFly, etc.) performs automatically:
- Parses the raw TCP bytes into an
HttpServletRequestobject. - Instantiates your servlet class once and calls
init(). - Allocates a thread from its pool for each incoming request and calls your
doGeton that thread. - Constructs the HTTP response headers from what you set on the
HttpServletResponseand serialises the body to the socket. - Calls
destroy()when the application is undeployed.
This division of labour — container handles the protocol plumbing, your code handles the application logic — is the core contract of the Servlet specification and the foundation of every Java web framework built on top of it.
Summary
A servlet is a class that extends HttpServlet and overrides the doXxx methods matching the HTTP verbs it handles. The @WebServlet annotation registers it with the container and declares the URL pattern. The HttpServletRequest parameter exposes every detail of the incoming request; the HttpServletResponse parameter lets you control every detail of the reply. Keep servlet code thin — read, delegate, respond — and put real logic in plain Java classes. In the next lesson you will see the full lifecycle: how the container creates, initialises, and eventually destroys a servlet.