The Servlet Lifecycle
🎧 Focus sounds
Soft beat sounds that many learners like while reading. Pick one, press Play, and keep studying.
Keep the volume low. These sounds are for comfort while studying, not a medical treatment. If you have epilepsy or are sensitive to pulsing sounds, use the background noise only.
In this lesson
A servlet is created by the container, used for many requests, and finally destroyed. This lifecycle is a classic interview question, and understanding it prevents serious bugs.
The lifecycle, animated#
Change the list of requests (try GET, POST, GET, GET) and watch which methods run:
- Load and instantiate: the container loads the class and creates one object (on the first request, or at startup).
- **
init(): called once**. Open resources, read configuration. - **
service(): called for every** request.HttpServlet.servicelooks at the HTTP method and callsdoGet,doPost,doPut,doDelete… - **
destroy(): called once** when the app stops or is redeployed. Release resources.
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
@WebServlet(urlPatterns = "/counter", loadOnStartup = 1,
initParams = @WebInitParam(name = "greeting", value = "Welcome"))
public class CounterServlet extends HttpServlet {
private String greeting;
private final AtomicInteger hits = new AtomicInteger(); // thread-safe counter
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config); // keep the config available
greeting = config.getInitParameter("greeting");
log("CounterServlet initialised");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().println(greeting + "! You are visitor #" + hits.incrementAndGet());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
hits.set(0);
resp.getWriter().println("Counter reset");
}
@Override
public void destroy() {
log("CounterServlet destroyed after " + hits.get() + " hits");
}
}
loadOnStartup = 1creates the servlet when the app starts instead of on the first request (no slow first hit). Lower numbers load first.- Init parameters configure a servlet without changing code.
One object, many threads#
This is the most important point: the container creates one instance of your servlet and calls service() from many threads at the same time, one per concurrent request.
Request A ──thread-1──┐
Request B ──thread-2──┼──▶ the SAME CounterServlet object
Request C ──thread-3──┘
So instance fields are shared by all users. This is a bug:
@WebServlet("/profile")
public class ProfileServlet extends HttpServlet {
private String currentUser; // ❌ shared by everyone!
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
currentUser = req.getParameter("user");
// ... another request may overwrite currentUser right here ...
resp.getWriter().println("Profile of " + currentUser); // may show someone else's name!
}
}
Rules:
- Keep per-request data in local variables (each thread has its own stack).
- Per-user data goes in the session (lesson 5).
- Shared data must be thread-safe (
AtomicInteger,ConcurrentHashMap) or immutable.
The same rule applies to Spring: controllers and services are singletons too. Never store request or user data in their fields.
The ServletContext: app-wide data#
Every web app has one ServletContext, shared by all servlets:
getServletContext().setAttribute("appName", "VitalStack");
String name = (String) getServletContext().getAttribute("appName");
String version = getServletContext().getInitParameter("version"); // from web.xml <context-param>
Use it for app-wide settings and shared resources (like a connection pool).
Lifecycle order with filters and listeners#
When the app starts: ServletContextListener.contextInitialized → filters’ init → servlets with loadOnStartup. When it stops, the reverse: servlets’ destroy → filters’ destroy → contextDestroyed. You’ll meet filters and listeners in lesson 6.
Common mistakes#
- Overriding
init(ServletConfig)without callingsuper.init(config), thengetServletConfig()returns null. (Or override the no-argumentinit()instead.) - Mutable instance fields used per request.
- Heavy work in
doGetthat should be done once ininit. - Overriding
service()and forgetting thedoXxxdispatch.
Exercise#
- Add
log()calls toinit,doGetanddestroy, deploy, make some requests, then stop Tomcat. Check the order in the logs. - Fix
ProfileServletabove by using a local variable. - Create a servlet that counts hits per page using a
ConcurrentHashMap<String, AtomicInteger>keyed byreq.getParameter("page").
Hint for 3
hits.computeIfAbsent(page, k -> new AtomicInteger()).incrementAndGet() is atomic and thread-safe.
Interview corner#
- Explain the servlet lifecycle. Load → instantiate →
init()once →service()per request (dispatching to doGet/doPost…) →destroy()once. - How many servlet instances are created? One per servlet declaration (per container), shared by all threads.
- Is a servlet thread-safe? Not automatically. Avoid mutable instance state or synchronise access.
- What is load-on-startup? It makes the container initialise the servlet at application startup, in the given order.
- ServletConfig vs ServletContext? Config is per servlet (init params); context is per web app (shared attributes).
Summary#
initonce,service/doXxxper request,destroyonce.- One servlet object handles many concurrent threads: no per-request data in fields.
ServletConfigholds per-servlet settings;ServletContextis app-wide.
Next: requests, responses, forms, redirects and forwards.
Quick quiz
3 questions. Check what stuck and earn 2 XP for each right answer.
Progress is saved in this browser. Create a free account to keep it on every device.
Get new lessons in your inbox
One email when we publish a new tutorial or guide. No spam, unsubscribe in one click.