Statelessness
Definition

Why Statelessness Matters

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

REST and Statelessness

When Stateful is Acceptable

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
