Question
How Java Servlets Work: Instantiation, Sessions, Instance Variables, and Multithreading
Question
Suppose I have a web server that hosts multiple Java servlets. To pass information between those servlets, I am using session attributes and servlet instance variables.
If two or more users send requests to the server, what happens to the session data?
- Is the session data shared by all users?
- Or does each user get a different session?
- If each user gets a different session, how does the server distinguish one user from another?
I also have a related question about servlet instantiation.
If n users access the same servlet, is that servlet instantiated only once when the first request arrives, or is a separate servlet object created for each user?
In other words, what happens to servlet instance variables when multiple users access the same servlet at the same time?
Short Answer
By the end of this page, you will understand that a Java servlet is typically instantiated once by the servlet container and then used to serve many requests concurrently. You will also learn that session data is usually separate for each user, while instance variables belong to the shared servlet object and can therefore be accessed by multiple threads at the same time. This distinction is essential for writing correct, thread-safe servlet code.
Concept
In Java web applications, a servlet is a class managed by a servlet container such as Tomcat or Jetty. The container is responsible for:
- creating the servlet object
- initializing it
- routing HTTP requests to it
- managing sessions
- handling concurrency
One servlet instance, many requests
For a standard servlet, the container usually creates one instance of the servlet class for a given servlet definition. That single object is then reused for many incoming requests.
That means this is the common lifecycle:
- The container creates the servlet instance.
- It calls
init()once. - For each request, it calls
service(), which dispatches todoGet(),doPost(), and so on. - When the application shuts down, it may call
destroy().
Because the same servlet object handles many requests, servlet instance variables are shared across requests and users.
Sessions are per user
A session is different. Session data is not stored in the servlet object itself. It is stored by the container in an HttpSession object.
In most cases:
- each user gets a separate session
- session attributes belong to that user’s session
- one user cannot automatically see another user’s session data
For example:
Mental Model
Think of a servlet like a single restaurant kitchen station.
- The servlet object is the station itself.
- The instance variables are items placed on the shared counter at that station.
- The requests are food orders arriving from many customers.
- The threads are cooks working at the same station at the same time.
- The session is each customer’s personal order ticket.
If a cook writes customer-specific information on the shared counter, another cook may accidentally read or overwrite it. That is what happens when you store request-specific or user-specific data in servlet instance variables.
But if each customer has their own ticket stored separately, the kitchen can keep orders apart. That is similar to using HttpSession for per-user data.
So:
- instance variables = shared space
- session attributes = customer-specific storage
- method local variables = temporary notes used by one cook for one order
Syntax and Examples
The core servlet concepts usually look like this:
import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
public class VisitServlet extends HttpServlet {
private int totalVisits = 0; // shared across all users and requests
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
Integer userVisits = (Integer) session.getAttribute("userVisits");
if (userVisits == null) {
userVisits = 0;
}
userVisits++;
session.setAttribute("userVisits", userVisits);
totalVisits++;
response.setContentType("text/plain");
response.getWriter().println( + userVisits);
response.getWriter().println( + totalVisits);
}
}
Step by Step Execution
Consider this servlet:
public class CounterServlet extends HttpServlet {
private int sharedCounter = 0;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
HttpSession session = request.getSession();
Integer sessionCounter = (Integer) session.getAttribute("sessionCounter");
if (sessionCounter == null) {
sessionCounter = 0;
}
sessionCounter++;
session.setAttribute("sessionCounter", sessionCounter);
sharedCounter++;
response.getWriter().println(
"Session counter = " + sessionCounter + ", shared counter = " + sharedCounter
);
}
}
Now imagine this sequence:
Request 1: User A visits
- The container creates the servlet instance if it does not already exist.
sharedCounterstarts at0.
Real World Use Cases
Where sessions are used
Sessions are commonly used for per-user state, such as:
- login status
- shopping cart contents
- multi-step form progress
- user preferences like theme or locale
- temporary data between pages
Example:
session.setAttribute("cartItemCount", 3);
session.setAttribute("loggedInUser", user);
Where servlet shared state may appear
Shared servlet state is less common for mutable data, but you may see it used for:
- read-only configuration loaded in
init() - references to shared services
- caches managed safely through dedicated components
- counters or metrics handled using thread-safe classes
Example of a safer shared object:
private final java.util.concurrent.atomic.AtomicInteger totalRequests = new AtomicInteger();
In real applications
Developers usually avoid storing changing business data directly in servlet instance fields. Instead, they use:
- databases
- services managed by dependency injection frameworks
- session storage for per-user temporary state
- application-wide context only for shared configuration
Real Codebase Usage
In real servlet-based applications, developers typically follow a few practical rules.
1. Use local variables for request data
Anything derived from the current request should usually stay inside the method:
String email = request.getParameter("email");
This avoids shared-state bugs.
2. Use session attributes for user-specific state
A logged-in user, a cart, or a wizard step often belongs in the session:
request.getSession().setAttribute("loggedInUserId", userId);
3. Use guard clauses for validation
Servlets often reject invalid input early:
String id = request.getParameter("id");
if (id == null || id.isBlank()) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing id");
return;
}
4. Avoid mutable servlet fields
Instead of this:
private String currentOrderId;
developers prefer:
Common Mistakes
1. Storing user-specific data in instance variables
Broken example:
public class ProfileServlet extends HttpServlet {
private String username;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
username = request.getParameter("username");
response.getWriter().println(username);
}
}
Why it is wrong
username is shared by all requests. One user can overwrite another user’s value.
Better approach
String username = request.getParameter("username");
or, if it must persist for a user:
request.getSession().setAttribute("username", username);
2. Assuming sessions are shared across all users
Beginners sometimes think HttpSession is global. It is not. It is generally tied to one client session.
3. Assuming one servlet object per user
Comparisons
| Concept | Scope | Shared Between Users? | Lifetime | Thread-Safety Concerns |
|---|---|---|---|---|
Local variable in doGet() / doPost() | Current method call | No | One request | Usually safe |
| Instance variable in servlet | Servlet object | Yes | As long as servlet lives | Yes |
HttpSession attribute | One user session | Usually no | Until session expires or is invalidated | Possible with concurrent requests from same user |
ServletContext attribute | Whole application | Yes |
Cheat Sheet
Quick rules
- A servlet is usually instantiated once per servlet definition.
- The container uses the same servlet instance for many requests.
doGet()anddoPost()may run concurrently in different threads.- Instance variables are shared across users and requests.
- Local variables are per request.
- Session attributes are usually per user.
- The server identifies sessions using a session ID such as
JSESSIONID.
Safe places to store data
Per request
String id = request.getParameter("id");
Per user session
request.getSession().setAttribute("cart", cart);
Shared read-only config
private String apiBaseUrl;
@Override
public void init() {
apiBaseUrl = getServletConfig().getInitParameter("apiBaseUrl");
}
Avoid
FAQ
Is a Java servlet created once or once per user?
Usually once per servlet definition, not once per user. The same servlet object typically handles many users.
Are servlet instance variables shared between users?
Yes. Instance variables belong to the shared servlet object, so all requests can access them.
Is HttpSession shared by all users?
No. Each user normally gets a separate session identified by a session ID.
How does the server know which session belongs to which user?
Usually through a session ID sent by the browser, often in a JSESSIONID cookie.
Are local variables inside doGet() thread-safe?
They are usually safe because each request has its own method call stack.
Can session attributes have concurrency issues?
Yes. If the same user sends multiple requests at the same time, session data can still be updated concurrently.
Should I store counters in servlet instance fields?
Only if you intentionally want shared state and use thread-safe tools like AtomicInteger.
Where should I store logged-in user data?
Usually in the session, or in a security framework designed for authentication state.
Mini Project
Description
Build a small servlet that tracks two kinds of counts: how many times the current user has visited the page and how many total requests the servlet has handled across all users. This project demonstrates the difference between session-scoped data and shared servlet state, and shows how to make shared counters thread-safe.
Goal
Create a servlet where each user sees their own visit count, while the application also tracks a safe global request count.
Requirements
- Create a servlet that responds to
GETrequests. - Store a per-user visit count in the session.
- Store a global request count using a thread-safe shared variable.
- Return both counts in the response.
- Ensure the global counter is safe under concurrent access.
Keep learning
Related questions
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Choosing a @NotNull Annotation in Java: Validation vs Static Analysis
Learn how Java @NotNull annotations differ, when to use each one, and how to choose between validation, IDE hints, and static analysis tools.
Convert a Java Stack Trace to a String
Learn how to convert a Java exception stack trace to a string using StringWriter and PrintWriter, with examples and common mistakes.