websockets
spring boot
kubernetes
microservices
cloud deployment

Websockets Spring boot Kubernetes

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

WebSockets work well with Spring Boot, but running them in Kubernetes adds operational constraints that normal REST services do not face. A WebSocket connection is long-lived, stateful at the transport level, and sensitive to load balancer and ingress behavior. To run this stack reliably, you need both correct application configuration and cluster-level networking choices that respect persistent connections.

Basic Spring Boot WebSocket Setup

Spring Boot can expose a WebSocket endpoint with a handler and a simple configuration class.

java
1import org.springframework.context.annotation.Configuration;
2import org.springframework.web.socket.TextMessage;
3import org.springframework.web.socket.WebSocketSession;
4import org.springframework.web.socket.config.annotation.EnableWebSocket;
5import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
6import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
7import org.springframework.web.socket.handler.TextWebSocketHandler;
8
9@Configuration
10@EnableWebSocket
11public class WebSocketConfig implements WebSocketConfigurer {
12    @Override
13    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
14        registry.addHandler(new ChatHandler(), "/ws").setAllowedOrigins("*");
15    }
16}
17
18class ChatHandler extends TextWebSocketHandler {
19    @Override
20    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
21        session.sendMessage(new TextMessage("echo: " + message.getPayload()));
22    }
23}

This is enough for a local proof of concept. The harder part comes after you scale the service horizontally.

Kubernetes Changes the Runtime Model

In Kubernetes, multiple Pods may serve the same service. A WebSocket connection stays attached to one Pod after the initial upgrade, which means:

  • in-memory session state is local to that Pod
  • reconnects may land on a different Pod
  • broadcasting across clients needs cross-Pod coordination

That is why a WebSocket service that works fine on one instance can behave unpredictably once replicas are added.

Keep Stateful Session Data Out of Pod Memory

If the application stores client presence, subscriptions, or room membership only in local memory, scaling becomes fragile. When a Pod dies, that state disappears. When another Pod needs to broadcast to all sessions, it cannot see sessions held elsewhere.

A better architecture uses a shared broker or store:

  • Redis Pub/Sub
  • Kafka
  • STOMP broker relay
  • database-backed session metadata, if latency allows

The rule is simple: local WebSocket connection objects stay in the Pod, but shared message-routing state must be externalized.

Ingress and Load Balancer Considerations

Your ingress controller or load balancer must support connection upgrades and sufficiently long idle timeouts. A typical NGINX Ingress annotation pattern looks like this:

yaml
1apiVersion: networking.k8s.io/v1
2kind: Ingress
3metadata:
4  name: ws-ingress
5  annotations:
6    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
7    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
8spec:
9  rules:
10    - host: ws.example.com
11      http:
12        paths:
13          - path: /
14            pathType: Prefix
15            backend:
16              service:
17                name: ws-service
18                port:
19                  number: 8080

If the idle timeout is too short, healthy WebSocket connections will be dropped even when the app is fine.

Service and Deployment Basics

The Kubernetes Service itself is standard, but you should think about readiness carefully. A Pod that is technically running but not ready to accept WebSocket connections should not receive traffic.

yaml
1apiVersion: v1
2kind: Service
3metadata:
4  name: ws-service
5spec:
6  selector:
7    app: ws-app
8  ports:
9    - port: 8080
10      targetPort: 8080

Deployment example:

yaml
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: ws-app
5spec:
6  replicas: 3
7  selector:
8    matchLabels:
9      app: ws-app
10  template:
11    metadata:
12      labels:
13        app: ws-app
14    spec:
15      containers:
16        - name: app
17          image: myorg/ws-app:latest
18          ports:
19            - containerPort: 8080

That gets traffic flowing, but not necessarily broadcasting correctly across Pods. That part still requires shared messaging.

Broadcasting in a Multi-Pod Setup

If one client on Pod A sends a message that should reach clients connected to Pods B and C, the app needs an external fan-out mechanism. In Spring, a common answer is using a broker or broker relay rather than manual in-memory broadcasting only.

A local ConcurrentHashMap of sessions can handle one Pod. It is not a cluster-wide broadcast solution.

Readiness, Draining, and Shutdown

WebSockets complicate rolling deployments because clients may keep long-lived connections open. Your shutdown flow should:

  • stop accepting new connections
  • allow old connections to drain when possible
  • close sessions cleanly before Pod termination

If termination is abrupt, clients must reconnect and your app should be prepared for that behavior.

Common Pitfalls

The biggest mistake is assuming WebSockets scale like stateless HTTP handlers. They do not. Long-lived connections create operational coupling between ingress, Pod lifecycle, and session routing.

Another issue is keeping important routing or presence state only in Pod memory. That works until you scale or restart.

Developers also often forget ingress timeout settings, which leads to random disconnects blamed on the application layer.

Summary

  • Spring Boot can expose WebSocket endpoints easily, but Kubernetes adds connection-lifecycle and scaling concerns.
  • Long-lived WebSocket sessions are bound to individual Pods after connection upgrade.
  • Shared message-routing state should live outside Pod memory in a broker or shared backend.
  • Ingress timeout and upgrade support must be configured correctly.
  • Plan for reconnects, rolling restarts, and cross-Pod broadcasting from the start.

Course illustration
Course illustration

All Rights Reserved.