🌐 Section 6 · Question #2

What does Stateless mean in REST context

In REST architecture, Statelessness is a foundational architectural constraint formulated by Roy Fielding. It dictates that the server must not store any client conversational c...


🟢 Junior Level

In REST architecture, Statelessness is a foundational architectural constraint formulated by Roy Fielding. It dictates that the server must not store any client conversational context or session state between successive HTTP requests.

Every incoming request from a client must be completely self-contained. It must convey all the contextual information, parameters, credentials, and identity data required for the server to understand, authenticate, and process it independently.

[Client] ─── Request 1: "Get Profile" (Token: ABC) ───► [Server Pod 1]
[Client] ─── Request 2: "Get Orders"  (Token: ABC) ───► [Server Pod 2] ◄── Handled seamlessly by any node!

Real-World Analogy: Airport Border Control

Consider the passport control counter at an international airport:

  • The border patrol officer does not memorize your face or maintain a notepad logging that you stood in line three minutes ago.
  • Each time you approach a checkpoint booth, you present your passport and boarding pass—all the necessary evidence in a single transaction.
  • Because the interaction is stateless, you can approach any available officer at any booth, and they can inspect and stamp your passport immediately.

Key Architectural Advantages

  1. Effortless Horizontal Scalability: Because server nodes retain zero client session context, load balancers (AWS ALB, Nginx, Envoy, Kubernetes Ingress) can distribute requests across server instances using simple Round-Robin or Least Connections algorithms without session stickiness.
  2. Resilience & Fault Tolerance: If an application instance crashes midway through a multi-step user interaction, the user does not experience a session outage. The next request is automatically routed to a healthy adjacent node.
  3. Optimized Memory Footprint: The application server does not reserve JVM heap memory for storing millions of inactive HttpSession objects, eliminating session serialization overhead and garbage collection pauses.
  4. Simplified Zero-Downtime Deployments: Nodes can be drained, updated, and restarted during rolling updates or blue-green deployments without migrating active session caches between pods.

🟡 Middle Level

Session State vs. Resource State

The most common misconception regarding statelessness is confusing conversational session state with domain resource state:

State Type Where It Resides Permitted in REST? Concrete Examples
Session State (Conversational Context) Strictly on the Client ❌ Forbidden on the server Active JWT access token, current shopping cart before checkout, multi-step registration wizard step, UI tab selection, pagination page index.
Resource State (Domain Data) On the Server (Persistent Storage) ✅ Permitted & Required Database records in PostgreSQL/MongoDB, confirmed customer orders, inventory stock levels, user profile records.

Configuring Stateless Architecture in Spring Boot 3 & Spring Security 6

In traditional stateful Java EE/Servlet applications, the container generates an HttpSession linked to a JSESSIONID cookie stored in the client’s browser. In a modern REST API, this mechanism is explicitly disabled:

package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class StatelessSecurityConfig {

    private final JwtAuthenticationFilter jwtAuthFilter;

    public StatelessSecurityConfig(JwtAuthenticationFilter jwtAuthFilter) {
        this.jwtAuthFilter = jwtAuthFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            // CSRF protection relies on server-stored CSRF tokens or cookies; 
            // it is redundant for purely stateless REST APIs authenticating via Authorization headers.
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> 
                // Instructs Spring Security NEVER to create or use an HttpSession
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/auth/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
    }
}
package com.example.config;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Collections;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        
        // In Stateless REST, the client supplies its identity token with every HTTP invocation
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String jwt = authHeader.substring(7);
            if (validateToken(jwt)) {
                UsernamePasswordAuthenticationToken authentication = 
                        new UsernamePasswordAuthenticationToken("user", null, Collections.emptyList());
                // The SecurityContext is created transiently for the duration of this single request thread
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        
        try {
            filterChain.doFilter(request, response);
        } finally {
            // Guarantee context clearance after request completion
            SecurityContextHolder.clearContext();
        }
    }

    private boolean validateToken(String token) {
        return token != null && !token.isBlank();
    }
}

🔴 Senior Level

The Stateless Token Revocation Paradox (JWT Invalidation Dilemma)

While JSON Web Tokens (JWT) are widely promoted as the textbook implementation of stateless authentication, enterprise security requirements reveal an architectural trade-off: instant revocation.

The Paradox Mechanics:

  • A standard JWT is self-contained: it encapsulates user claims, expiration timestamps, and a cryptographic digital signature (e.g., HMAC-SHA256 or RSA-256).
  • A backend service validates the token purely by checking its signature using a local public key—zero database or cache queries required.
  • However, if a user changes their password, is terminated from their organization, or their device is compromised, security policy mandates immediate token revocation.
  • To invalidate a stateless token before its expiration (exp), the server must check every incoming request against a centralized blacklist (e.g., a Redis store checking the jti token ID).
  • The Paradox: The moment you query a centralized Redis instance on every HTTP request to verify if a token was revoked, your architecture is no longer truly stateless in performance or dependency; it re-introduces a centralized stateful lookup and a potential single point of failure (SPOF)!
                    ┌──────────────────────────┐
                    │   Stateless REST Node    │
                    └─────────────┬────────────┘
                                  │ Queries on EVERY HTTP request:
                                  ▼
                    ┌──────────────────────────┐
                    │   Redis Token Blacklist  │ ◄── Centralized dependency & latency bottleneck
                    └──────────────────────────┘

Production Best Practice Solution:

  1. Short-Lived Access Tokens: Issue access tokens with an ultra-short lifespan (5–10 minutes). They are verified purely through signature calculations without network I/O.
  2. Long-Lived Refresh Tokens: Issue refresh tokens with extended lifetimes (7–30 days) stored securely in a database or Redis.
  3. Database checks occur only once every 5–10 minutes during token refresh cycles, reducing database I/O by over 99% while bounding the maximum vulnerability window to the access token’s short TTL.

Optimistic Concurrency Control in Stateless Environments: ETag & If-Match

In a stateless architecture, servers cannot hold database locks across user interactions (e.g., while a user reviews an edit screen). If two users attempt to edit the same record concurrently, the system risks the Lost Update Problem.

REST solves this statelessly via protocol-level HTTP Conditional Requests (RFC 9110):

// 1. Client A and Client B retrieve current order state:
GET /api/v1/orders/100 HTTP/1.1
Host: api.example.com

HTTP/1.1 200 OK
ETag: "v1-987abc"
Content-Type: application/json

{"status": "PENDING", "amount": 500}
// 2. Client A submits an update specifying the exact version precondition:
PUT /api/v1/orders/100 HTTP/1.1
Host: api.example.com
If-Match: "v1-987abc"
Content-Type: application/json

{"status": "CONFIRMED", "amount": 500}

HTTP/1.1 200 OK
ETag: "v2-456def"
// 3. Client B attempts to overwrite without refreshing:
PUT /api/v1/orders/100 HTTP/1.1
Host: api.example.com
If-Match: "v1-987abc"
Content-Type: application/json

{"status": "CANCELLED", "amount": 500}

// The server detects version mismatch and rejects the mutation statelessly:
HTTP/1.1 412 Precondition Failed
Content-Type: application/problem+json

{
  "title": "Precondition Failed",
  "status": 412,
  "detail": "Resource was modified by another transaction. Reload and retry."
}

4 Tricky Questions

1. Does Statelessness mean that a REST API cannot use a database or write data to disk?

Answer: No. This is a common conflation between Session State and Resource State:

  • Statelessness strictly prohibits storing client conversational state (such as active login session context, intermediate form values, or uncommitted shopping carts) in server memory across requests.
  • Resource State represents the persistent domain data of the application (customer accounts, inventory levels, financial transactions). Storing and mutating resource state in relational databases, document stores, or file systems is the primary purpose of business APIs and is fully compliant with REST constraints.

2. What happens to the SecurityContext across requests when SessionCreationPolicy.STATELESS is enabled in Spring Security?

Answer: When SessionCreationPolicy.STATELESS is configured:

  1. Spring Security replaces the default HttpSessionSecurityContextRepository with NullSecurityContextRepository (or RequestAttributeSecurityContextRepository in modern Spring Security 6).
  2. The framework never persists the SecurityContext into an HttpSession at the end of the request thread, and never looks up an existing session when a new request arrives.
  3. Every request begins with an empty SecurityContext. A filter in the chain (such as JwtAuthenticationFilter) must authenticate the incoming request credentials (e.g., Bearer token) and populate the SecurityContextHolder anew.
  4. When the HTTP response is written, the SecurityContext is discarded from thread-local memory.

3. What are Sticky Sessions, and why are they considered an anti-pattern in modern cloud-native REST architectures?

Answer: Sticky Sessions (Session Affinity) is a load-balancer mechanism that inspects client cookies (e.g., JSESSIONID) or IP addresses to route all subsequent requests from a specific user to the exact same physical server node hosting their in-memory session.

In cloud-native REST systems, Sticky Sessions are an anti-pattern because:

  1. Uneven Workload Distribution: If a disproportionate number of high-activity users are pinned to a single server instance, that instance experiences resource exhaustion while other nodes remain idle.
  2. Broken Auto-Scaling: When Kubernetes Horizontal Pod Autoscaler (HPA) adds new pods, sticky users remain pinned to existing pods, preventing new instances from absorbing traffic.
  3. Session Loss on Pod Recycling: Cloud containers are ephemeral. When a pod is terminated during a rolling deployment or crash, all users pinned to that node have their conversational sessions dropped instantly.

4. If a client sends an multi-step wizard payload across 5 successive requests, how is statelessness preserved?

Answer: In a stateless architecture, intermediate wizard steps must not be stored in a server-side session:

  1. Client-Side State Storage (Preferred for Drafts): The client (e.g., Single-Page Application) keeps intermediate wizard inputs in local memory, localStorage, or IndexedDB. Only upon final submission does the client dispatch the consolidated resource payload via POST /api/v1/applications.
  2. Draft Resource Pattern (Server-Side Resource State): If intermediate draft persistence is required (e.g., allowing resume across devices), the server models the draft as a first-class domain resource in the database (POST /api/v1/application-drafts). Each step performs a PATCH /api/v1/application-drafts/{id}. This preserves statelessness because the draft is persistent Resource State, not volatile in-memory session context.

🎯 Interview Cheat Sheet

30-Second Elevator Pitch

Statelessness in REST mandates that the server retains zero client conversational context across request boundaries. Every request must be fully self-contained, supplying complete authentication and operation data (such as a JWT Bearer token).

  • Session State (conversational context) lives strictly on the client.
  • Resource State (persistent domain data) lives in server databases.
  • Benefits: Eliminates sticky sessions, unlocks effortless horizontal scaling across Kubernetes clusters, optimizes JVM memory, and guarantees resilience against node crashes.

Red Flags (What NOT to Say)

  • ❌ “Statelessness means the backend has no database and cannot persist data.” (Fails the fundamental distinction between Session State and Resource State).
  • ❌ “In REST, we store the logged-in user in the Tomcat HttpSession.” (Storing conversational session state on the server directly violates Fielding’s constraints).
  • ❌ “Sticky Sessions are a best practice for balancing traffic in modern REST microservices.” (Sticky sessions are an outdated workaround for legacy stateful monoliths).
  • ❌ “To make an API stateless, you must disable database transactions.” (ACID transactions govern resource state mutations and are completely independent of HTTP session statelessness).