Skip to content

Statelessness

Definition

Statelessness Definition


Why Statelessness Matters

Benefits of Statelessness


Stateful vs Stateless Examples

// STATEFUL: Server maintains session
public class StatefulShoppingCart {
    // State stored on server per user
    private Map<String, List<Item>> userCarts = new HashMap<>();
    private Map<String, String> sessionToUser = new HashMap<>();

    public void addItem(String sessionId, Item item) {
        String userId = sessionToUser.get(sessionId);  // Lookup session
        userCarts.computeIfAbsent(userId, k -> new ArrayList<>())
                 .add(item);
    }

    public List<Item> getCart(String sessionId) {
        String userId = sessionToUser.get(sessionId);  // Need session state
        return userCarts.getOrDefault(userId, Collections.emptyList());
    }
    // Problems:
    // - Server must be sticky (session affinity)
    // - Can't easily scale horizontally
    // - Server restart loses carts
}

// STATELESS: All state in request/external store
public class StatelessShoppingCart {
    private final CartRepository cartRepo;  // External storage

    public void addItem(String userId, Item item) {
        // userId from JWT token, not server session
        Cart cart = cartRepo.findByUserId(userId)
            .orElse(new Cart(userId));
        cart.addItem(item);
        cartRepo.save(cart);
    }

    public Cart getCart(String userId) {
        return cartRepo.findByUserId(userId)
            .orElse(new Cart(userId));
    }
    // Benefits:
    // - Any server can handle any request
    // - Easy horizontal scaling
    // - State persisted externally
}

Where to Store State

External State Storage


REST and Statelessness

REST Statelessness Constraint


When Stateful is Acceptable

When Stateful is OK


Stateless Design Patterns

// Pattern 1: Token-based Authentication
@RestController
public class OrderController {

    @GetMapping("/orders")
    public List<Order> getOrders(@AuthenticationPrincipal User user) {
        // User extracted from JWT token in request
        // No server-side session lookup
        return orderService.getOrdersForUser(user.getId());
    }
}

// Pattern 2: Request-scoped Context
public class RequestContext {
    private final String userId;
    private final String correlationId;
    private final Instant requestTime;

    // All context passed with request, not stored on server
}

// Pattern 3: Continuation Token for Pagination
@GetMapping("/items")
public PagedResponse<Item> getItems(
        @RequestParam(required = false) String continuationToken) {

    // Token contains: lastId, sortOrder, filters
    // Client sends token back for next page
    // Server doesn't track pagination state

    return itemService.getPage(decodeContinuationToken(continuationToken));
}

// Pattern 4: Idempotency Keys
@PostMapping("/payments")
public Payment createPayment(
        @RequestBody PaymentRequest request,
        @RequestHeader("Idempotency-Key") String idempotencyKey) {

    // Idempotency key allows safe retries
    // State (whether processed) stored externally, not in server memory
    return paymentService.processWithIdempotency(request, idempotencyKey);
}

Tips & Tricks

Statelessness Tips