Servlets Tutorial: Learn Web Components from Scratch (2026)
Servlets are the foundation of Java web development. As server-side components that process HTTP requests and generate responses, they form the basis for every Java web framework — Spring MVC itself is built on top of the Servlet API. Understanding servlets gives you insight into how HTTP lifecycle, session management, and request dispatching work at the container level.
This tutorial covers servlet lifecycle, request/response handling, session management, filters, listeners, and deployment descriptors. While modern applications often use higher-level frameworks, the concepts remain relevant for debugging and customization.
Servlet Lifecycle and Configuration
Every servlet has a well-defined lifecycle: init() is called once when the servlet is first loaded; service() (or doGet, doPost) handles each request; destroy() cleans up when the servlet is taken out of service. The ServletConfig object provides initialization parameters, and ServletContext provides container-wide configuration.
Servlets are configured via web.xml or @WebServlet annotation. The annotation approach reduces XML boilerplate: @WebServlet("/api/orders") with init parameters via @WebInitParam. The container manages servlet instantiation and threading — servlets are singletons shared across requests.
@WebServlet(
name = "OrderServlet",
urlPatterns = "/api/orders/*",
initParams = @WebInitParam(name = "defaultPageSize", value = "50")
)
public class OrderServlet extends HttpServlet {
@Override
public void init() throws ServletException {
String pageSize = getInitParameter("defaultPageSize");
setPageSize(Integer.parseInt(pageSize));
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter();
out.write(orderService.findAll());
}
}
Request and Response Handling
HttpServletRequest provides access to HTTP method, URI, parameters, headers, cookies, and request body. Use getParameter() for query/form parameters, getHeader() for HTTP headers, and getInputStream() for reading request bodies. The request also provides attribute storage — setAttribute() and getAttribute() for passing objects between servlets via RequestDispatcher.
HttpServletResponse controls status code, headers, and response body. Set Content-Type before writing to the output stream. Use sendRedirect() for client-side redirects and sendError() for error responses. Response buffering improves performance — set buffer size via setBufferSize().
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
try (BufferedReader reader = req.getReader();
JsonWriter writer = Json.createWriter(resp.getWriter())) {
JsonObject json = Json.createReader(reader).readObject();
String customerId = json.getString("customerId");
BigDecimal amount = new BigDecimal(json.getString("amount"));
Order order = orderService.create(customerId, amount);
resp.setStatus(HttpServletResponse.SC_CREATED);
writer.writeObject(orderToJson(order));
} catch (Exception e) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
}
}
Session Management
HttpSession tracks user state across multiple requests. The container sends a JSESSIONID cookie (or URL rewriting) to identify returning clients. Call request.getSession() to obtain or create a session, session.setAttribute() to store objects, and session.invalidate() to terminate it.
Sessions consume server memory. Configure session timeout in web.xml or programmatically. For distributed applications, replicate sessions across nodes or switch to client-side tokens with server-side cache-backed sessions.
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
User user = authenticationService.authenticate(username, password);
if (user != null) {
HttpSession session = req.getSession(true);
session.setAttribute("user", user);
session.setMaxInactiveInterval(1800); // 30 minutes
resp.sendRedirect("/dashboard");
} else {
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid credentials");
}
}
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.getSession(false).invalidate();
resp.sendRedirect("/login");
}
}
Filters for Cross-Cutting Concerns
Filters intercept requests before they reach servlets and responses before they reach clients. Common use cases: request logging, authentication checks, compression, CORS headers, and XSS sanitization. Filters form a chain — each filter calls chain.doFilter() to pass the request to the next filter or target servlet.
Map filters via @WebFilter annotation on URL patterns. Order of execution is determined by web.xml ordering or the @Order annotation. Filters are ideal for concerns that apply uniformly across multiple servlets without modifying servlet code.
@WebFilter("/api/*")
@Order(1)
public class AuthenticationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
((HttpServletResponse) response).sendError(401);
return;
}
try {
User user = tokenService.validate(token.substring(7));
req.setAttribute("currentUser", user);
chain.doFilter(request, response);
} catch (Exception e) {
((HttpServletResponse) response).sendError(401, "Invalid token");
}
}
}
Listeners and Context Events
Servlet listeners react to lifecycle events in the web container. ServletContextListener hooks into application startup and shutdown — use it to initialize shared resources like connection pools or schedulers. HttpSessionListener tracks session creation and destruction for monitoring or cleanup.
ServletRequestListener fires on each request and can be used for request logging or clearing thread-local state. Implement multiple listener interfaces in one class if concerns are related. Listeners are configured via @WebListener annotation or web.xml.
@WebListener
public class AppContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext ctx = sce.getServletContext();
String dbUrl = ctx.getInitParameter("database.url");
DataSource ds = createConnectionPool(dbUrl);
ctx.setAttribute("dataSource", ds);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
DataSource ds = (DataSource) sce.getServletContext()
.getAttribute("dataSource");
if (ds != null) {
// close pool
}
}
}
Async Servlets and Non-Blocking IO
Servlet 3.0 introduced asynchronous processing via startAsync(). The request thread returns to the container immediately while processing continues in another thread. This prevents thread exhaustion during long-running operations like WebSocket connections or Server-Sent Events.
Servlet 3.1 added non-blocking IO — readListener and writeListener interfaces for processing request/response bodies without blocking container threads. Combine async with non-blocking IO for maximum scalability in high-concurrency scenarios.
@WebServlet(urlPatterns = "/api/async/orders", asyncSupported = true)
public class AsyncOrderServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
AsyncContext asyncCtx = req.startAsync();
asyncCtx.setTimeout(30_000);
// Submit long-running task
executorService.submit(() -> {
try {
List orders = orderService.findAll();
resp.setContentType("application/json");
resp.getWriter().write(toJson(orders));
} catch (Exception e) {
resp.setStatus(500);
} finally {
asyncCtx.complete();
}
});
}
}
Frequently Asked Questions
What is the difference between Servlet and JSP?
Servlets are Java classes for request/response processing, ideal for controllers and API endpoints. JSP is a template technology for generating HTML with embedded Java code. In modern MVC, servlets act as controllers and JSP (or Thymeleaf) as views.
How do I upload a file with Servlets?
Annotate the servlet with @MultipartConfig, access uploaded parts via request.getPart("file"), and write them using part.write("/path/filename.ext"). Configure max file and request sizes in @MultipartConfig attributes.
What is the difference between forward and redirect?
forward (req.getRequestDispatcher("/path").forward()) is server-side — the browser URL stays unchanged. redirect (resp.sendRedirect("/path")) sends a 302 to the browser, which makes a new request. Use forward for internal navigation and redirect after POST to avoid duplicate submissions.
Are servlets still relevant with Spring Boot?
Yes — Spring MVC and Spring Boot run on top of the Servlet API. Understanding servlets helps with debugging filters, configuring embedded Tomcat, and implementing custom authentication or compression that interacts directly with the container.
Originally published on Ayodhyyya. Last updated June 1, 2026.