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:
HttpSession session = request.getSession();
session.setAttribute("username", "alice");
That username value is stored in Alice's session, not globally for all users.
How the server knows which session belongs to which user
The container usually tracks the session using a session ID:
- commonly stored in a cookie such as
JSESSIONID - sometimes passed in the URL if cookies are disabled
When the browser sends the next request, it includes the session ID. The server uses that ID to look up the correct HttpSession object.
Why this matters
This distinction is critical:
- local variables inside
doGet()ordoPost()are usually safe because each thread has its own stack - session attributes are user-specific, but can still require care if the same user sends concurrent requests
- instance variables are shared and can cause race conditions if mutated without proper synchronization
A lot of beginner servlet bugs come from putting per-request or per-user data into servlet instance fields.
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("Your visits: " + userVisits);
response.getWriter().println("Total visits: " + totalVisits);
}
}
What this example shows
userVisitsis stored in the session, so it is usually different for each user.totalVisitsis an instance variable, so it is shared by everyone.totalVisits++is not thread-safe. Multiple requests at the same time can corrupt the value.
Safer approach
Keep request-specific values in local variables and user-specific values in the session:
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String productId = request.getParameter("productId"); // local to this request
HttpSession session = request.getSession();
session.setAttribute("lastViewedProduct", productId); // per user session
response.getWriter().println("Last viewed product saved in your session.");
}
Avoid this pattern
public class BadServlet extends HttpServlet {
private String currentUser;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
currentUser = request.getParameter("username");
response.getWriter().println("Hello " + currentUser);
}
}
This is broken because currentUser is shared by all requests. Two users hitting the servlet at nearly the same time can overwrite each other’s data.
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.- User A gets a session, for example with ID
ABC123. sessionCounterfor A isnull, so it becomes0.sessionCounter++makes it1.sharedCounter++makes it1.- Output might be:
Session counter = 1, shared counter = 1
Request 2: User A visits again
- Same servlet instance is used.
- Browser sends session ID
ABC123. - Server finds User A’s existing session.
sessionCounteris now1for A.- It becomes
2. sharedCounterbecomes2.- Output might be:
Session counter = 2, shared counter = 2
Request 3: User B visits
- Same servlet instance is still used.
- User B has a different session ID, maybe
XYZ999. - User B’s
sessionCounterstarts asnull. - It becomes
1. sharedCounterbecomes3.- Output might be:
Session counter = 1, shared counter = 3
Key observation
- User A and User B have different session counters.
- Both users share the same
sharedCounterbecause it is an instance field on the single servlet object.
Concurrency warning
If User A and User B hit the servlet at exactly the same time, the increments to sharedCounter can overlap. Without synchronization or atomic types, the result may be wrong.
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:
- request parameters
- session attributes
- database lookups
- service-layer state management
5. Use thread-safe helpers for shared counters or caches
If shared mutable state is absolutely necessary, use tools like:
AtomicInteger- concurrent collections
- synchronization where appropriate
Example:
private final java.util.concurrent.atomic.AtomicInteger totalRequests = new java.util.concurrent.atomic.AtomicInteger();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
int current = totalRequests.incrementAndGet();
response.getWriter().println("Total requests: " + current);
}
6. Keep servlets thin
In larger codebases, servlets often only:
- read request data
- validate input
- call a service class
- write the response
This reduces the chance of mixing request state, session state, and shared application state in unsafe ways.
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
A servlet is typically not created once per user. One instance usually serves many users.
4. Forgetting thread safety
Broken example:
private int count = 0;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
count++;
response.getWriter().println(count);
}
Problem
count++ is not atomic. Concurrent requests can produce incorrect values.
Safer version
private final java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger();
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.getWriter().println(count.incrementAndGet());
}
5. Putting large or sensitive data in the session without care
Sessions consume memory. Also, sensitive data should be minimized and handled carefully.
6. Ignoring that one user can make concurrent requests too
Even session data may need care. A single browser can trigger multiple parallel requests, so session attributes are not automatically safe from all race conditions.
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 | Application lifetime | Yes |
Servlet instance vs session
| Feature | Servlet Instance Variable | Session Attribute |
|---|---|---|
| Stored where? | In the servlet object | In HttpSession |
| Per user? | No | Yes, usually |
| Shared globally? | Yes, for that servlet instance | No |
| Good for user cart/login state? | No | Yes |
| Needs thread-safety? | Yes | Sometimes |
init() vs doGet() / doPost()
| Method | Called When | Typical Use |
|---|---|---|
init() | Once when servlet is initialized | Load config, create shared resources |
doGet() / doPost() | On every request | Handle request-specific logic |
Session vs cookie
| Feature | Session | Cookie |
|---|---|---|
| Stored on server? | Yes | No |
| Stored in browser? | Session ID usually is | Yes |
| Good for larger user state? | Yes | Usually no |
| Easy to tamper with directly? | Session data no, cookie contents yes | 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
private String currentUser;
private int requestCount;
unless you truly want shared state and handle thread safety correctly.
Thread-safe counter example
private final java.util.concurrent.atomic.AtomicInteger requestCount = new java.util.concurrent.atomic.AtomicInteger();
Session basics
HttpSession session = request.getSession();
session.setAttribute("name", "Alice");
String name = (String) session.getAttribute("name");
Important edge case
A session is per user, but the same user can still send multiple requests at the same time. Do not assume session updates are automatically synchronized.
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
Add External JAR Files to an IntelliJ IDEA Java Project
Learn how to add external JAR dependencies to an IntelliJ IDEA Java project using module libraries, and when to use Maven or Gradle instead.
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.
Call a Method After a Delay in Android Java
Learn how to run Java code after a delay in Android using Handler.postDelayed, manage the main thread, and cancel callbacks safely.