Stateful vs Stateless Architecture in System Design
A stateless component does not remember anything about a client between requests: every request carries all the information needed to handle it. A stateful component keeps information about clients or ongoing work in its own memory or storage and relies on it for later requests. This single design choice decides how easily a system can scale, recover from failures and be deployed.
This tutorial explains what "state" means, how stateless and stateful components work, the different ways to handle user sessions, a runnable Java example that shows the difference behind a load balancer, how stateful components such as databases are scaled, and how Kubernetes treats each type. It closes with a comparison table, common mistakes and best practices.
1. What Is "State" in a System?
State is any information a system must remember to behave correctly over time. Examples:
- Who is logged in, and their permissions (session state).
- The items in a shopping cart before checkout.
- The step a user has reached in a multi-page form.
- An open WebSocket connection and the chat rooms it has joined.
- The data in a database: orders, accounts, products.
- The position of every player in a multiplayer game.
State itself is unavoidable: every useful application remembers something. The real design question is where the state lives. If it lives in the memory of a particular application server, that server becomes special and hard to replace. If it lives in a dedicated store, the application servers can be identical and disposable.
A helpful analogy: a stateless service is like a vending machine. It does not know who you are; you put in money and press a button, and it gives you a drink. A stateful service is like your personal bank adviser, who remembers your history from previous meetings. If that adviser is absent, the next person has to start from scratch.
2. What Is a Stateless Architecture?
In a stateless architecture, application servers keep no client-specific data in memory between requests. Each request includes everything needed to process it, such as an authentication token and the relevant IDs, and any data that must persist is read from and written to an external store.
Because every server is identical, you can:
- Scale horizontally by simply adding more servers.
- Balance load freely, using any algorithm, without tying users to servers.
- Survive failures: if a server crashes, the load balancer sends the next request elsewhere and nothing is lost.
- Deploy easily: replace servers one by one during a rolling update without logging anyone out.
Typical stateless components include REST APIs authenticated with tokens, web front-end servers, API gateways, image resizing services and serverless functions. Note that a stateless service can still use a database; it simply does not keep client data in its own memory.
3. What Is a Stateful Architecture?
In a stateful architecture, a server keeps data about a client or a task in its own memory, and later requests depend on that data. The classic example is a web application that stores the user's login session and cart in the server's memory (an in-memory HTTP session).
Because the data exists on only one server, the user's requests must keep returning to that server. Load balancers do this with sticky sessions (session affinity), usually based on a cookie.
Stateful designs have real advantages in some situations: data in local memory is extremely fast to access, and long-lived connections (WebSockets, game sessions, streaming) are naturally stateful. But for ordinary web request handling, keeping state in application servers creates problems:
- Lost data on failure: a crash or restart wipes in-memory sessions.
- Uneven load: sticky sessions can leave some servers overloaded while others sit idle.
- Harder scaling: new servers only receive new users; existing users stay pinned to old servers.
- Painful deployments: every rolling update risks logging users out unless sessions are drained carefully.
4. Stateful vs Stateless: Comparison Table
| Aspect | Stateless | Stateful |
|---|---|---|
| Client data on the server | None kept between requests | Kept in memory or local storage |
| What each request contains | Everything needed (token, IDs, parameters) | Often just a session reference |
| Horizontal scaling | Easy: add identical servers | Harder: data must be partitioned or replicated |
| Load balancing | Any algorithm, any server | Sticky sessions or state-aware routing |
| Failure impact | Retry on another server, nothing lost | State on the failed server may be lost |
| Deployments | Simple rolling updates | Need draining, migration or replication |
| Performance | May need a lookup in an external store | Fast local access to state |
| Complexity | Lower for the service; state moves to dedicated stores | Higher: replication, consistency, recovery |
| Examples | REST APIs, web servers, serverless functions, gateways | Databases, caches, message brokers, WebSocket and game servers |
5. Seeing the Difference in Java
The program below simulates two servers behind a round-robin load balancer. A user adds three items to a cart, and each request lands on the next server. First the servers keep carts in their own memory (stateful), then they use a shared store (stateless):
package com.javabytechie.state;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Two servers behind a round-robin load balancer: carts kept in server memory vs in a shared store. */
public class StatefulVsStatelessDemo {
interface CartServer {
List<String> addItem(String sessionId, String item);
}
/** Stateful: each server remembers carts in its own memory. */
static class StatefulServer implements CartServer {
private final Map<String, List<String>> carts = new HashMap<>();
public List<String> addItem(String sessionId, String item) {
List<String> cart = carts.computeIfAbsent(sessionId, k -> new ArrayList<>());
cart.add(item);
return cart;
}
}
/** Stateless: servers keep nothing; carts live in a shared store (Redis or a database in real life). */
static class StatelessServer implements CartServer {
private final Map<String, List<String>> sharedStore;
StatelessServer(Map<String, List<String>> sharedStore) { this.sharedStore = sharedStore; }
public List<String> addItem(String sessionId, String item) {
List<String> cart = sharedStore.computeIfAbsent(sessionId, k -> new ArrayList<>());
cart.add(item);
return cart;
}
}
static void shop(String title, CartServer[] servers) {
System.out.println(title);
String[] items = {"keyboard", "mouse", "monitor"};
for (int i = 0; i < items.length; i++) {
CartServer server = servers[i % servers.length]; // round-robin load balancing
List<String> cart = server.addItem("session-42", items[i]);
System.out.println(" request " + (i + 1) + " -> server " + (i % servers.length + 1) + " : cart = " + cart);
}
}
public static void main(String[] args) {
shop("STATEFUL servers (cart in server memory)", new CartServer[] { new StatefulServer(), new StatefulServer() });
Map<String, List<String>> redis = new HashMap<>();
shop("STATELESS servers (cart in shared store)", new CartServer[] { new StatelessServer(redis), new StatelessServer(redis) });
}
}
STATEFUL servers (cart in server memory)
request 1 -> server 1 : cart = [keyboard]
request 2 -> server 2 : cart = [mouse]
request 3 -> server 1 : cart = [keyboard, monitor]
STATELESS servers (cart in shared store)
request 1 -> server 1 : cart = [keyboard]
request 2 -> server 2 : cart = [keyboard, mouse]
request 3 -> server 1 : cart = [keyboard, mouse, monitor]
With stateful servers, the user's cart is split across two machines: server 2 never saw the keyboard, so the user's view jumps around between requests. With stateless servers sharing one store, every request sees the complete cart, no matter which server handles it. This is exactly the bug teams hit when they add a second server to an application that keeps sessions in memory.
6. Handling User Sessions: Four Approaches
User sessions are the most common source of unwanted state in web applications. There are four main ways to handle them:
| Approach | How it works | Pros | Cons |
|---|---|---|---|
| In-memory session (stateful) | Session lives in the app server's memory | Simplest, fastest | Lost on restart; needs sticky sessions |
| Sticky sessions | Load balancer pins each user to one server | Easy to add to existing apps | Uneven load; still loses sessions on failure |
| Shared session store | Sessions kept in Redis or a database, keyed by a session cookie | Servers become stateless; sessions survive restarts; can revoke sessions | Extra network hop; the store must be highly available |
| Self-contained tokens (JWT) | Signed token sent by the client on every request contains the user's identity and claims | No server-side session storage at all | Hard to revoke before expiry; keep tokens short-lived and small |
For most applications, a shared session store or short-lived tokens are the right choice. Both make the application tier stateless while still remembering who the user is.
7. Making a Spring Boot Application Stateless
Option 1: Move HTTP sessions to Redis with Spring Session. Your code keeps using HttpSession as before, but the data is stored in Redis instead of server memory. Add the spring-session-data-redis and spring-boot-starter-data-redis dependencies, then configure the connection:
spring:
data:
redis:
host: redis.internal
port: 6379
session:
timeout: 30m # sessions expire after 30 minutes of inactivity
server:
servlet:
session:
cookie:
secure: true
http-only: true
same-site: lax
package com.javabytechie.state;
import java.util.ArrayList;
import java.util.List;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CartController {
@PostMapping("/api/cart/items")
@SuppressWarnings("unchecked")
public List<String> addItem(@RequestParam String item, HttpSession session) {
// With Spring Session, this session is loaded from and saved to Redis,
// so any server instance sees the same cart.
List<String> cart = (List<String>) session.getAttribute("cart");
if (cart == null) {
cart = new ArrayList<>();
}
cart.add(item);
session.setAttribute("cart", cart); // write back so the change is persisted
return cart;
}
}
Option 2: Fully stateless APIs with tokens. For REST APIs used by mobile apps and single-page applications, tell Spring Security never to create a session, and authenticate every request with a bearer token:
package com.javabytechie.state;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
http
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable()) // no cookies, so no CSRF risk for this API
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults())); // validate the JWT on every request
return http.build();
}
}
Business data such as carts and orders still belongs in a database or cache, not in the token. Tokens should carry only identity and permissions.
8. Where Stateful Components Belong
The common architecture pattern is to push all state down into a small number of specialised, carefully managed stateful components, and keep everything above them stateless:
Stateful components cannot simply be copied, because every copy must hold the right data. They are scaled and protected with dedicated techniques:
- Replication: keep copies of the data on several nodes for availability and read capacity. See Database Replication.
- Partitioning (sharding): split the data so each node holds a portion. See Database Sharding.
- Consensus and leader election: make sure nodes agree on the current state and who may write it.
- Backups and point-in-time recovery: protect against mistakes and corruption.
- Managed services: cloud databases, caches and brokers handle much of this complexity for you.
Some application components are stateful by nature, such as WebSocket servers holding live connections, multiplayer game servers and stream processors. For these, keep durable data in shared stores, make clients reconnect gracefully, and use consistent routing so a client returns to the right node.
9. Stateless and Stateful Protocols
| Protocol | Type | Why |
|---|---|---|
| HTTP | Stateless | Each request is independent; cookies and tokens add state on top when needed |
| DNS (over UDP) | Stateless | Each query and answer stands alone |
| TCP | Stateful | Both ends track sequence numbers and connection state |
| WebSocket | Stateful | A long-lived connection with context on both sides |
| FTP | Stateful | The server remembers the logged-in user and current directory |
HTTP being stateless is one of the main reasons the web scales so well: any server can answer any request.
10. Stateless and Stateful Workloads in Kubernetes
Kubernetes has separate building blocks for the two types:
- A Deployment runs identical, interchangeable pods. Pods get random names and can be replaced or rescheduled anywhere. It is designed for stateless services.
- A StatefulSet gives each pod a stable name (
db-0,db-1), a stable network identity and its own persistent volume that follows it if the pod is rescheduled. It starts and stops pods in order. It is designed for databases, brokers and other stateful systems.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: orders-db
spec:
serviceName: orders-db
replicas: 3
selector:
matchLabels:
app: orders-db
template:
metadata:
labels:
app: orders-db
spec:
containers:
- name: postgres
image: postgres:16
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates: # each pod (orders-db-0, -1, -2) gets its own persistent disk
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
Running databases yourself on Kubernetes is possible but demanding; many teams prefer managed database services and keep Kubernetes for stateless workloads.
11. When to Choose Each
Make it stateless (the default for application code):
- REST and GraphQL APIs, web front ends and API gateways.
- Any service you want to auto-scale, deploy frequently or run on serverless platforms.
- Workers that process jobs from a queue.
Accept statefulness where it is the job itself:
- Databases, caches, search engines and message brokers.
- Real-time connections: WebSockets, multiplayer games, video calls.
- Stream processing that keeps running aggregates, and workflows that track long-running progress.
12. Common Mistakes to Avoid
- Keeping sessions in server memory and only discovering the problem when a second server is added.
- Relying on sticky sessions as the fix, which still loses sessions on failure and causes uneven load.
- Storing uploads or generated files on the local disk of an application server; use object storage instead.
- In-memory caches holding authoritative data rather than copies that can be rebuilt.
- Putting large or sensitive data in JWTs, or making tokens long-lived and impossible to revoke.
- Making the shared session store a single point of failure; it needs replication and monitoring like any database.
- Scheduled jobs that assume a single server, which then run multiple times when the application scales out.
13. Best Practices
- Design application services to be stateless by default, and treat servers as disposable.
- Store sessions in a shared, replicated store, or use short-lived signed tokens.
- Keep durable data in databases and files in object storage, never on local disks of app servers.
- Concentrate state in a few specialised components, preferably managed services, and invest in their replication and backups.
- Make requests idempotent so they can safely be retried on another server.
- For naturally stateful services, support graceful reconnection and route clients consistently.
- Use distributed locks or a scheduler with leader election for jobs that must run once across many instances.
14. Summary
- State is information a system must remember; the key decision is where it lives.
- Stateless servers keep no client data between requests, so they scale, balance load, recover and deploy easily.
- Stateful servers keep data locally, which is fast but complicates scaling, failover and deployments.
- Handle sessions with a shared store or signed tokens rather than server memory and sticky sessions.
- Keep the application tier stateless and concentrate state in replicated databases, caches and brokers; use Kubernetes Deployments and StatefulSets accordingly.
"Stateless services, stateful stores" is one of the simplest and most effective rules in system design. Open the FAQ tab for answers to common questions about stateful and stateless architecture.
Frequently Asked Questions (FAQ)
Q1. What is the difference between stateful and stateless architecture?
A stateless component keeps no client-specific data between requests, so every request carries everything needed and any server can handle it. A stateful component remembers client data from earlier requests, so later requests depend on reaching the server that holds that data.
Q2. What does "state" mean in system design?
State is any information a system must remember to work correctly over time, such as login sessions, shopping carts, form progress, open connections, game positions and the data stored in databases.
Q3. Why are stateless services easier to scale?
Because all instances are identical and interchangeable. You can add or remove servers at any time, use any load-balancing algorithm and lose a server without losing user data, since the state lives in shared stores.
Q4. Is HTTP stateless or stateful?
HTTP is stateless: each request is independent and the protocol itself does not remember previous requests. Applications add state on top of HTTP using cookies, sessions or tokens.
Q5. Are REST APIs stateless?
Statelessness is one of REST's architectural constraints: each request must contain all the information the server needs, typically including an authentication token. Well-designed REST APIs do not depend on server-side sessions.
Q6. What are sticky sessions?
Sticky sessions, also called session affinity, make a load balancer send all of a user's requests to the same server, usually using a cookie. They let in-memory sessions work across multiple servers, but cause uneven load and lose sessions when a server fails.
Q7. How do you make a stateful web application stateless?
Move session data from server memory into a shared store such as Redis (for example with Spring Session), or switch to short-lived signed tokens like JWTs. Also move uploaded files to object storage and keep all business data in databases.
Q8. Is a database stateful or stateless?
A database is stateful: its whole purpose is to store and remember data. It is scaled and protected with replication, partitioning, backups and careful failover, rather than by simply adding identical copies.
Q9. Is JWT authentication stateless?
Yes. A JWT contains the user's identity and claims and is signed, so the server can verify it on every request without storing a session. The trade-off is that tokens are hard to revoke before they expire, so they should be short-lived.
Q10. What is the difference between a Kubernetes Deployment and a StatefulSet?
A Deployment runs identical, interchangeable pods for stateless services. A StatefulSet gives each pod a stable name, network identity and its own persistent volume, and starts and stops pods in order, which suits databases and other stateful systems.
Q11. Are WebSocket servers stateful?
Yes. Each server holds live connections and the context of each connected client. To scale them, keep durable data in shared stores, use a pub/sub layer to deliver messages across servers and make clients reconnect automatically.
Q12. Can a stateless service use a database?
Yes. "Stateless" means the service does not keep client data in its own memory between requests. It can still read and write a database, cache or other store on every request.
Q13. What are the disadvantages of stateless architecture?
Requests may be larger because they carry tokens and context, each request may need an extra lookup in an external store, and the shared stores themselves must be made highly available. Tokens can also be harder to revoke than server sessions.
Q14. What are the advantages of stateful architecture?
Local state is very fast to access, avoiding network lookups, and it fits naturally with long-lived interactions such as real-time connections, games and streaming. Stateful designs are also required for components whose job is to store data.
Q15. Where should I store user sessions in a scalable application?
In a shared, replicated store such as Redis or a database, or as short-lived signed tokens sent by the client. Avoid storing sessions in the memory of individual application servers.
Q16. Are serverless functions stateless?
Yes. Serverless functions should be treated as stateless: an instance may be created or destroyed at any time, and consecutive calls may run on different instances. Any data that must persist goes to databases, caches or object storage.
Q17. What happens to in-memory sessions during a deployment?
When a server is restarted or replaced during a deployment, sessions stored in its memory are lost, so users may be logged out or lose their cart. Shared session stores or tokens avoid this.
Q18. How do stateful systems handle failover?
They replicate state to other nodes, detect failures with health checks or heartbeats, and promote a replica or reassign partitions to healthy nodes. Consensus protocols ensure only one node acts as the leader, preventing conflicting writes.
Q19. Should microservices be stateless?
Generally yes. Microservices should keep no client state in memory so they can scale and be replaced freely, while each service stores its durable data in its own database. Naturally stateful services are the exception and need special handling.
Q20. How should I explain stateful vs stateless in an interview?
Define state, contrast stateless servers (interchangeable, easy to scale and recover) with stateful ones (local data, sticky sessions, harder failover), explain session handling with shared stores or JWTs, and summarise the rule of keeping services stateless and concentrating state in replicated data stores.