Servlet, JSP, and Filter Basics
Introduction
Chapter 08 deployed a single doGet Servlet. Real web apps use the full Servlet lifecycle, POST bodies, sessions, Filters that run before Servlets, and sometimes JSP for HTML. This chapter explains those container-native APIs—what Tomcat calls before your business logic—and how they relate to Spring MVC and Spring Boot filters you may already know.
Prerequisites
- Your First Servlet Application —
hello.wardeployed - Tomcat Directory Layout —
work/directory - web.xml and context.xml — filter ordering concepts
Servlet Lifecycle
Tomcat creates one Servlet instance per class (default) and reuses it for many requests across threads.
Code explanation:
init()— read config, open expensive resources (use carefully—prefer lazy init or DI frameworks)service()— dispatches todoGet,doPost, etc.destroy()— release resources; not guaranteed onkill -9
Example with logging:
Check logs/catalina.*.log or console when redeploying—you should see init once per deploy.
doGet vs doPost
| Method | Typical use | Read body |
|---|---|---|
| GET | Read-only, query params | Rarely |
| POST | Forms, create/update | Yes |
Query parameters (GET or POST URL):
String name = req.getParameter("name"); // /hello/api?name=AdaPOST form fields (application/x-www-form-urlencoded):
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
req.setCharacterEncoding("UTF-8");
String name = req.getParameter("name");
resp.setContentType("text/plain; charset=UTF-8");
resp.getWriter().println("Hello, " + name);
}Test:
curl -X POST http://127.0.0.1:8080/hello/api \
-d "name=Tomcat"JSON bodies — Servlets do not parse JSON automatically; read req.getInputStream() and use Jackson, or use Spring MVC @RequestBody. Container basics focus on streams and parameters.
Request and Response Objects
| Object | Role |
|---|---|
HttpServletRequest | Method, headers, params, body, session access |
HttpServletResponse | Status code, headers, writer/output stream |
ServletContext | App-wide attributes (getServletContext()) |
HttpSession | Per-browser conversation state |
ServletContext (application scope)
ServletContext ctx = getServletContext();
ctx.setAttribute("startupTime", System.currentTimeMillis());
Object val = ctx.getAttribute("startupTime");Shared across all users and threads in this web app—use for read-mostly config, not user-specific data.
HttpSession (user scope)
HttpSession session = req.getSession(true);
session.setAttribute("user", "ada");
String user = (String) session.getAttribute("user");Code explanation:
- Session ID usually in cookie
JSESSIONID - Default timeout from
web.xmlsession-config(see web.xml chapter) - Prefer stateless JWT APIs in modern apps—sessions still appear in legacy JSP apps
Spring equivalent: @SessionAttribute, Spring Security context—not identical but similar “conversation” ideas.
JSP Basics
JSP (JavaServer Pages) embed HTML with Java snippets. Tomcat compiles .jsp files into Servlets under work/:
hello.jsp → work/Catalina/localhost/hello/org/apache/jsp/hello_jsp.java
→ .class → executed like any ServletAdd src/main/webapp/time.jsp:
<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<body>
<p>Server time: <%= new java.util.Date() %></p>
</body>
</html>URL: http://localhost:8080/hello/time.jsp
Code explanation:
<%= … %>— expression output (learning only—avoid heavy logic in JSP)- First request may be slow (compile); later requests reuse class until file changes
- Modern stacks prefer React/Vue static + JSON API, or Thymeleaf with Spring—JSP remains common in older codebases
JSP “changes not visible”
| Fix | Action |
|---|---|
| Clear compile cache | Stop Tomcat; delete work/Catalina/localhost/hello/; start |
| Confirm deploy | Edit correct webapps/hello/time.jsp, not stale exploded copy |
| Production | Precompile JSPs or avoid JSP; do not rely on reloadable |
Filters
Filters wrap the request/response pipeline before and after Servlets—encoding, authentication, logging, CORS in simple apps.
Request → Filter1 → Filter2 → Servlet → Filter2 → Filter1 → ResponseExample UTF-8 encoding filter:
Code explanation:
@WebFilter("/*")— all paths in this appchain.doFilter— invoke next filter or target Servlet; skipping it blocks the request- Order:
@WebFilterorder orweb.xml<filter-mapping>sequence
Register in web.xml instead (explicit order):
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.example.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>Spring Boot parallel: FilterRegistrationBean, Spring Security filter chain—higher level, same pipeline idea.
Listeners
ServletContextListener runs code when the app is deployed or undeployed:
Messages appear in logs/localhost.*.log.
Use cases: warm caches, register drivers (prefer container pools today), cleanup on undeploy.
Error Handling in Servlets
Set HTTP status and message:
resp.sendError(HttpServletResponse.SC_NOT_FOUND, "User not found");Or forward to a JSP/HTML error page configured in web.xml error-page (see chapter 06).
Uncaught exceptions → 500; stack traces in logs/localhost.*.log, not always shown to clients (good for security).
Thread Safety
One Servlet instance, many threads. Do not store mutable request-specific state in instance fields:
// BAD — race between requests
private int counter;
// OK — local variables in doGet/doPost
int counter = 0;Share read-only resources or use proper concurrent structures; prefer stateless handlers.
Servlet API vs Spring MVC (Quick Map)
| Servlet API | Spring MVC (Boot) |
|---|---|
@WebServlet | @Controller / @RestController |
HttpServletRequest | @RequestParam, HttpServletRequest injection |
Filter | Filter, HandlerInterceptor, Security filters |
web.xml | application.yml, @Configuration |
| Manual JSON parsing | @RequestBody, Jackson auto |
Learning Servlets clarifies what Spring abstracts—useful when debugging WAR deploy on Tomcat without Boot magic.
Mini Lab: Extend hello.war
- Add
LifecycleServlet,EncodingFilter,time.jsp mvn clean package, redeploy WAR- Verify:
curl http://127.0.0.1:8080/hello/lifecycle
curl http://127.0.0.1:8080/hello/time.jsp
curl -X POST -d "name=Ada" http://127.0.0.1:8080/hello/api- Read
logs/localhost.*.logfor listener and filter activity
Common Issues
| Issue | Cause | Fix |
|---|---|---|
| POST body empty / wrong encoding | Missing UTF-8 on request | EncodingFilter; req.setCharacterEncoding |
| Filter blocks all requests | No chain.doFilter | Add call in doFilter |
| JSP compile error | Syntax in JSP | Fix JSP; read stack trace in log |
| Session always null | Cookies disabled; wrong app | Browser cookies; correct context path |
| 405 Method Not Allowed | GET only Servlet hit with POST | Implement doPost or use GET |
Post-Chapter Checklist
- Draw lifecycle: init → service/doGet/doPost → destroy
- Use
HttpSessionandServletContextappropriately - Explain Filter chain and
chain.doFilter - Know JSP compiles under
work/ - Relate Filters to Spring Security conceptually
What Comes Next
| Chapter | Topic |
|---|---|
| 10 — Logging and access log | AccessLogValve, log files |
| 14 — Spring Boot WAR on Tomcat | Framework on same container |
| 20 — Troubleshooting | Deeper diagnostics |
FAQ
One Servlet instance per request?
No—one instance per Servlet class (singleton default). Many concurrent requests share it—write thread-safe code.
JSP vs Servlet for HTML?
Historically JSP for HTML, Servlet for control. Today: static frontend + JSON API, or server templates (Thymeleaf). JSP maintenance still exists in legacy apps.
Filter vs Servlet?
Filter — cross-cutting pipeline wrapper, no direct URL target (unless chain ends early). Servlet — primary endpoint handler.
Where are sessions stored?
Tomcat default: in-memory on that node. Clustering/replication is advanced—not covered here.
Use @WebFilter and web.xml together?
Possible—know ordering rules. Prefer one style per app for clarity.
Replace Servlets with Spring Boot?
For new JSON APIs, usually yes. For learning Tomcat ops and maintaining WARs, Servlets remain relevant.