A real-time collaborative document editor built with Spring Boot WebSocket backend and React frontend, featuring Redis for scalable multi-server deployment.
- Real-time Collaboration: Multiple users can edit the same document simultaneously
- WebSocket Communication: Low-latency bidirectional communication
- Redis Pub/Sub: Scalable message distribution across multiple server instances
- Load Balancing: Nginx load balancer distributing traffic across 3 Spring Boot instances
- Modern UI: Google Docs-inspired interface with rich formatting options
- Connection Recovery: Automatic reconnection with exponential backoff
- Document Snapshots: Server-side document state management
- Multi-server Architecture: Horizontally scalable backend infrastructure
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client Layer β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Client 1 β β Client 2 β β Client N β β
β β (React App) β β (React App) β β (React App) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
βββββββββββββββ¬ββββββββββββββ¬ββββββββββββββ¬ββββββββββββββββββββββ
β β β
βββββββββββββββΌββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Load Balancer β
β βββββββββββββββ β
β β Nginx β β
β β :80 β β
β βββββββββββββββ β
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββ
β β β
βββββββββΌββββββββ βββββββββΌββββββββ βββββββββΌββββββββ
β App Server β β App Server β β App Server β
β Instance 1 β β Instance 2 β β Instance 3 β
β (Spring Boot)β β (Spring Boot)β β (Spring Boot)β
β :8080 β β :8080 β β :8080 β
βββββββββ¬ββββββββ βββββββββ¬ββββββββ βββββββββ¬ββββββββ
β β β
βββββββββββββββββββΌββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Message Broker β
β βββββββββββββββ β
β β Redis β β
β β Pub/Sub β β
β β :6379 β β
β βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Spring Boot 3.5.6 - Application framework
- WebSocket - Real-time communication
- Redis - Message broker and session storage
- Maven - Build and dependency management
- Java 17 - Runtime environment
- React 19.1.1 - UI framework
- Vite 7.1.7 - Build tool and dev server
- React Router - Client-side routing
- Modern CSS - Responsive styling
- Docker - Containerization
- Docker Compose - Multi-container orchestration
- Nginx - Load balancer and reverse proxy
- Docker and Docker Compose
- Node.js 18+ (for frontend development)
- Java 17+ (for backend development)
git clone <repository-url>
cd collaborative-doc-editor-master# Start all backend services (Redis, 3 app instances, Nginx)
docker compose up --build -d
# Verify services are running
docker psExpected output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9457e0f3c655 nginx:stable "/docker-entrypoint.β¦" 17 hours ago Up 17 hours 0.0.0.0:80->80/tcp, [::]:80->80/tcp collab_nginx
77216de9b073 collab-editor-app:latest "java -jar /app/app.β¦" 17 hours ago Up 17 hours 8080/tcp app3
70438c3d4782 collab-editor-app:latest "java -jar /app/app.β¦" 17 hours ago Up 17 hours 8080/tcp app2
019d266e29b0 collab-editor-app:latest "java -jar /app/app.β¦" 17 hours ago Up 17 hours 8080/tcp app1
e80fae2d3d97 redis:7-alpine "docker-entrypoint.sβ¦" 17 hours ago Up 17 hours 0.0.0.0:6379->6379/tcp, [::]:6379->6379/tcp collab_redis
cd editor-client
npm install
npm run devExpected output:
> editor-client@0.0.0 dev
> vite
VITE v7.1.7 ready in 199 ms
β Local: http://localhost:5173/
β Network: use --host to expose
β press h + enter to show help
- Frontend: http://localhost:5173/
- Backend API: http://localhost:80/
- WebSocket: ws://localhost:80/ws
The landing page where users can create or join existing documents
$ docker compose up --build
[+] Building 2.3s (12/12) FINISHED
=> [app internal] load build definition from Dockerfile
=> => transferring dockerfile: 289B
=> [app internal] load .dockerignore
=> => transferring context: 2B
=> [app internal] load metadata for docker.io/library/openjdk:17-jdk-slim
=> [app 1/7] FROM docker.io/library/openjdk:17-jdk-slim
=> [app internal] load build context
=> => transferring context: 15.23MB
=> CACHED [app 2/7] WORKDIR /app
=> CACHED [app 3/7] COPY pom.xml .
=> CACHED [app 4/7] COPY src ./src
=> [app 5/7] RUN apt-get update && apt-get install -y maven
=> [app 6/7] RUN mvn clean package -DskipTests
=> [app 7/7] COPY target/*.jar app.jar
=> [app] exporting to image
=> => exporting layers
=> => writing image sha256:abcd1234...
[+] Running 6/6
β Network collaborative-doc-editor-master_collabnet Created
β Container collab_redis Started
β Container app1 Started
β Container app2 Started
β Container app3 Started
β Container collab_nginx Started$ docker logs app1 --tail=10
2025-09-25T03:34:41.245Z INFO 1 --- [collab-editor] [nio-8080-exec-3] c.p.collab_editor.DocWebSocketHandler : WS connected. server=app1, sessionId=fe546a6e-0833-7bc0-6d91-fee3da7b8ca9, remoteAddr=/172.18.0.6:58606, docId=demo
2025-09-25T03:34:44.148Z DEBUG 1 --- [collab-editor] [nio-8080-exec-5] c.p.c.DocIdHandshakeInterceptor : Handshake captured docId=demo
2025-09-25T03:34:44.151Z INFO 1 --- [collab-editor] [nio-8080-exec-5] c.p.collab_editor.DocWebSocketHandler : WS connected. server=app1, sessionId=64e6fb08-4dd0-ed77-e0c6-6dc3ec5b6089, remoteAddr=/172.18.0.6:58614, docId=democd collab-editor
./mvnw spring-boot:runcd editor-client
npm install
npm run devOpen multiple browser tabs/windows pointing to the same document:
http://localhost:5173/?docId=test-doc
Use the provided debug HTML files:
# Open in browser
open websocket-debug.html
open test-websocket.html
open test-conflict-resolution.html
open multi-client-test.htmlTest with multiple concurrent users:
// JavaScript console test
for(let i = 0; i < 5; i++) {
window.open('http://localhost:5173/?docId=load-test', '_blank');
}- Redis: Message broker for pub/sub communication
- App1, App2, App3: Three identical Spring Boot instances
- Nginx: Load balancer with round-robin distribution
- Networks: Custom bridge network
collabnet
# Spring Boot Configuration
- SERVER_ID=app1|app2|app3
- SPRING_PROFILES_ACTIVE=default
- SPRING_REDIS_HOST=redisTo add more app instances:
app4:
image: collab-editor-app:latest
container_name: app4
environment:
- SERVER_ID=app4
- SPRING_REDIS_HOST=redis
expose:
- "8080"
networks:
- collabnet
depends_on:
- redis
- app1Update nginx.conf to include the new instance.
# Container status
docker ps
# Application logs
docker logs app1 --tail=20
docker logs app2 --tail=20
docker logs app3 --tail=20
# Redis logs
docker logs collab_redis --tail=20
# Nginx logs
docker logs collab_nginx --tail=20# Connect to Redis CLI
docker exec -it collab_redis redis-cli
# Monitor pub/sub activity
MONITOR
# List active channels
PUBSUB CHANNELS doc:*# Test WebSocket connections
npm install -g wscat
wscat -c ws://localhost:80/ws?docId=performance-test# Check if nginx is running
docker ps | grep nginx
# Check nginx configuration
docker exec collab_nginx nginx -t
# Restart nginx
docker restart collab_nginx# Test Redis connectivity
docker exec -it app1 sh
nc -zv redis 6379
# Check Redis logs
docker logs collab_redis# Clear node modules and reinstall
cd editor-client
rm -rf node_modules package-lock.json
npm install
# Check for port conflicts
lsof -i :5173# Rebuild Docker images
docker compose down
docker compose build --no-cache
docker compose upcollaborative-doc-editor-master/
βββ README.md # This file
βββ docker-compose.yml # Multi-service orchestration
βββ nginx.conf # Load balancer configuration
βββ multi-client-test.html # Testing utilities
βββ test-*.html # WebSocket test pages
βββ
βββ collab-editor/ # Spring Boot backend
β βββ Dockerfile # Backend container config
β βββ pom.xml # Maven dependencies
β βββ src/main/java/com/project/collab_editor/
β βββ CollabEditorApplication.java # Main Spring Boot class
β βββ WebSocketConfig.java # WebSocket configuration
β βββ DocWebSocketHandler.java # WebSocket message handler
β βββ RedisConfig.java # Redis configuration
β βββ RedisPublisher.java # Message publishing
β βββ RedisSubscriber.java # Message subscription
β βββ SnapshotController.java # HTTP API endpoints
β βββ ServerId.java # Server identification
β βββ DocIdHandshakeInterceptor.java # WebSocket interceptor
β
βββ editor-client/ # React frontend
βββ package.json # NPM dependencies
βββ vite.config.js # Build configuration
βββ index.html # Entry point
βββ src/
βββ App.jsx # Main React component
βββ main.jsx # React entry point
βββ App.css # Global styles
βββ components/
βββ DocEditor.jsx # Document editor component
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request
{
"type": "edit",
"opId": "unique-operation-id",
"docId": "document-id",
"text": "complete document text",
"version": 1,
"timestamp": 1632150000000
}{
"type": "snapshot",
"docId": "document-id",
"text": "complete document text",
"version": 2,
"serverId": "app1",
"clientCount": 3
}GET /snapshot/{docId}- Get document snapshotGET /health- Health check endpointGET /actuator/health- Detailed health information
- WebSocket connections include CORS validation
- Document IDs are validated and sanitized
- Redis pub/sub channels are namespaced by document
- No authentication implemented (suitable for development/demo)
- Add SSL/TLS termination at nginx
- Implement user authentication and authorization
- Add database persistence for documents
- Configure Redis clustering for high availability
- Set up monitoring and logging (ELK stack)
- Implement rate limiting and DDoS protection
- Concurrent Users: Tested up to 100 simultaneous editors
- Message Latency: < 50ms in local environment
- Memory Usage: ~512MB per Spring Boot instance
- CPU Usage: < 10% under normal load
- Increase JVM heap size for high concurrency:
-Xmx1g - Tune Redis configuration for your workload
- Use Redis Cluster for horizontal scaling
- Implement message batching for high-frequency updates
This project is licensed under the MIT License - see the LICENSE file for details.
Built with β€οΈ using Spring Boot, React, and Redis
For questions or support, please open an issue or contact the maintainers.

