Chatter is a real-time chat application built with Spring Boot and React.
The application uses Keycloak for authentication and authorization, WebSockets with STOMP for real-time communication, MySQL for persistent data, and Flyway for database migrations.
Users can authenticate through Keycloak, view other registered users, see their online/offline presence, start conversations, exchange messages in real time, receive delivery and read-status updates, and share image, audio, and video files.
The project is designed as a practical implementation of modern backend concepts including OAuth2/JWT security, WebSockets, message delivery states, presence tracking, file storage, JPA/Hibernate, database migrations, and Docker Compose.
- π Keycloak-based authentication and JWT security
- π€ Automatic synchronization of authenticated Keycloak users into MySQL
- π₯ View other registered users
- π’ Real-time online/offline presence tracking
- π¬ Real-time one-to-one messaging using WebSockets and STOMP
- π Persistent chat history
- β‘ Optimistic UI for faster message interaction
- π€ Message delivery states:
SENT,DELIVERED,SEEN - π Read receipts for messages
- π Automatic delivery of messages sent while a user was offline
- π Upload image, audio, and video files
- π Authenticated file downloads restricted to chat participants
- π JWT authentication for both REST APIs and WebSocket connections
- π Automatic JWT refresh before API/WebSocket operations
- ποΈ MySQL persistence with Spring Data JPA
- π Database schema management with Flyway
- π³ Containerized with Docker and Docker Compose
- π React frontend served through Nginx in Docker
- User opens the Chatter frontend.
- User is authenticated through Keycloak.
- Keycloak issues a JWT access token.
- React sends the token with authenticated REST requests and WebSocket connections.
- Spring Security validates the JWT.
- The authenticated user is synchronized with the MySQL
userstable. - The user can view other registered users.
- The frontend establishes a STOMP connection through
/ws. - The backend tracks the user's online presence.
- User selects another contact and loads the existing chat history.
- Messages are sent through
/app/chat.send. - The backend persists the message and sends it to the recipient through a user-specific queue.
- Message state changes from
SENTtoDELIVEREDwhen the recipient is online. - Messages become
SEENwhen the recipient reads the conversation. - Presence changes are broadcast to subscribed clients.
- Media files are uploaded through the REST API and their URLs are sent as chat messages.
ββββββββββββββββ
β React β
β Frontend β
ββββββββ¬ββββββββ
β
β Login
βΌ
ββββββββββββββββ
β Keycloak β
β chatter β
β realm β
ββββββββ¬ββββββββ
β
β JWT Access Token
βΌ
ββββββββββββββββ
β React β
ββββββββ¬ββββββββ
β
β Bearer Token
βΌ
ββββββββββββββββ
β Spring Boot β
β Security β
ββββββββ¬ββββββββ
β
β JWT validation
βΌ
ββββββββββββββββ
β User Sync β
β MySQL β
ββββββββββββββββ
User A
β
β STOMP SEND
β /app/chat.send
βΌ
βββββββββββββββββββββββ
β Spring WebSocket β
β ChatController β
ββββββββββββ¬βββββββββββ
β
β Save message
βΌ
βββββββββββββββββββββββ
β MySQL β
β messages β
ββββββββββββ¬βββββββββββ
β
β User destination
βΌ
βββββββββββββββββββββββ
β /user/queue/messagesβ
ββββββββββββ¬βββββββββββ
β
βΌ
User B
WebSocket CONNECT
β
βΌ
JwtChannelInterceptor
β
βΌ
PresenceTracker
β
βββ First active session
β β
β βΌ
β User ONLINE
β β
β βΌ
β /topic/presence.{userId}
β
βΌ
WebSocket DISCONNECT
β
βΌ
PresenceTracker
β
βββ Last active session
β β
β βΌ
β User OFFLINE
β β
β βΌ
β /topic/presence.{userId}
β
βΌ
Update lastSeen in MySQL
The application uses Keycloak to authenticate users before allowing access to Chatter.
Users can select another registered user and exchange messages in real time.
The contact list displays the current presence state of other users and updates it through WebSocket events.
Messages support the following states:
SENT β DELIVERED β SEEN
Users can upload and send:
- πΌοΈ Images
- π΅ Audio
- π₯ Video
Files are stored locally by the backend and accessed through authenticated file endpoints.
| Category | Technology |
|---|---|
| Language | Java 21 |
| Backend Framework | Spring Boot 4.1.0 |
| Frontend | React 19 |
| Build Tool | Maven |
| Database | MySQL 8 |
| ORM | Spring Data JPA / Hibernate |
| Authentication | Keycloak 26.5.7 |
| Security | Spring Security OAuth2 Resource Server / JWT |
| Real-Time Communication | WebSocket + STOMP |
| WebSocket Client | @stomp/stompjs + SockJS |
| Database Migration | Flyway |
| File Storage | Local File System |
| Frontend Build Tool | Vite |
| Frontend Server | Nginx |
| Containerization | Docker / Docker Compose |
| Utilities | Lombok |
Chatter
βββ backend
β βββ src
β β βββ main
β β β βββ java
β β β β βββ com.ragul.Chatter
β β β β βββ chat
β β β β βββ common
β β β β βββ config
β β β β βββ exception
β β β β βββ file
β β β β βββ message
β β β β βββ user
β β β β βββ websocket
β β β β βββ config
β β β β
β β β βββ resources
β β β βββ application.yaml
β β β βββ db
β β β βββ migration
β β β βββ V1__init_user_schema.sql
β β β βββ V2__init_chat_schema.sql
β β β βββ V3__init_message_schema.sql
β β β
β β βββ test
β β
β βββ keycloak
β β βββ realm-export.json
β βββ Dockerfile
β βββ pom.xml
β
βββ Frontend
β βββ src
β β βββ api.js
β β βββ App.jsx
β β βββ ChatWindow.jsx
β β βββ ContactList.jsx
β β βββ AuthedMedia.jsx
β β βββ MessageStatus.jsx
β β βββ keycloak.js
β β βββ main.jsx
β β βββ socket.js
β βββ Dockerfile
β βββ nginx.conf
β βββ package.json
β
βββ keycloak
β βββ chatter-realm.json
β
βββ docker-compose.yml
All protected REST endpoints require a valid Keycloak JWT access token.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/users |
Get all users except the authenticated user |
| GET | /api/users/{userId}/presence |
Get a user's current presence |
| GET | /api/chats/{recipientId}/messages |
Get chat history with a user |
| POST | /api/files/upload |
Upload an image, audio, or video file |
| GET | /files/{filename} |
Download an uploaded file |
WebSocket endpoint:
/ws
SockJS is used by the React frontend for the WebSocket connection.
| Destination | Description |
|---|---|
/app/chat.send |
Send a message |
/app/chat.read |
Mark chat messages as read |
| Destination | Description |
|---|---|
/user/queue/messages |
Receive real-time messages |
/user/queue/messages.status |
Receive message delivery/read-status updates |
/user/queue/errors |
Receive WebSocket message errors |
/topic/presence.{userId}
Clients subscribe to a user's presence topic to receive online/offline updates.
Destination:
/app/chat.send
Payload:
{
"recipientId": "recipient-user-id",
"content": "Hello from Chatter!",
"type": "TEXT"
}Supported message types:
TEXT
IMAGE
AUDIO
VIDEO
{
"id": 1,
"chatId": "chat-id",
"senderId": "sender-user-id",
"recipientId": "recipient-user-id",
"content": "Hello from Chatter!",
"type": "TEXT",
"state": "SENT",
"createdDate": "2026-08-11T12:30:00"
}Message state progresses through:
SENT
β
DELIVERED
β
SEEN
Chatter uses Keycloak as the identity and access management server.
The authentication flow is based on:
- OpenID Connect
- OAuth 2.0
- JWT access tokens
- Spring Security OAuth2 Resource Server
The frontend uses keycloak-js to authenticate users.
The backend validates the JWT for protected REST requests.
WebSocket connections are also authenticated using the JWT supplied through the STOMP Authorization header.
The backend synchronizes authenticated Keycloak users into the local MySQL users table.
Flyway automatically manages the database schema.
Stores authenticated application users.
id
first_name
last_name
email
last_seen
created_date
last_modified_date
Stores conversations between users.
id
sender_id
recipient_id
created_date
last_modified_date
Stores messages and their delivery state.
id
content
state
type
chat_id
sender_id
recipient_id
created_date
last_modified_date
SENT
DELIVERED
SEEN
Chatter supports uploading:
- Images
- Audio
- Video
Uploaded files are stored in the configured upload directory.
Default configuration:
app:
upload:
dir: uploadsThe Docker deployment mounts this directory to a persistent Docker volume:
/uploads
File downloads are protected by checking whether the authenticated user is a participant in the message containing the file.
The local development configuration uses:
spring:
datasource:
url: jdbc:mysql://localhost:3307/chatter
username: root
password: root
jpa:
hibernate:
ddl-auto: validate
flyway:
enabled: true
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://127.0.0.1:9090/realms/chatterFor Docker, the backend uses the MySQL and Keycloak Docker service names for internal communication where required.
Default local Keycloak address:
http://127.0.0.1:9090
Realm:
chatter
Client:
chatter-client
Keycloak is imported from the realm configuration during Docker startup.
Do not use real production credentials in the repository. Replace development credentials before deploying the application outside a local environment.
git clone https://github.com/<your-username>/Chatter.git
cd ChatterMake sure Docker is installed and running.
Start the complete application stack:
docker compose up --buildThe React application is available at:
http://localhost:5173
The Spring Boot API is available at:
http://localhost:8080
Keycloak is available at:
http://127.0.0.1:9090
Chatter uses Docker Compose to run the application services.
docker compose up --builddocker compose up -ddocker compose psdocker compose logs -fdocker compose down| Service | Container | Port | Purpose |
|---|---|---|---|
| MySQL | mysql_chatter |
3307 |
Application database |
| Keycloak | keycloak_chatter |
9090 |
Authentication server |
| Backend | backend |
8080 |
Spring Boot API and WebSocket server |
| Frontend | frontend |
5173 |
React application served by Nginx |
Chatter maintains message state using:
βββββββββββββ
β SENT β
βββββββ¬ββββββ
β
β Recipient online
βΌ
βββββββββββββ
β DELIVERED β
βββββββ¬ββββββ
β
β Recipient reads chat
βΌ
βββββββββββββ
β SEEN β
βββββββββββββ
If a recipient is offline, the message remains in the SENT state.
When the recipient establishes a WebSocket connection again, pending messages for that user are marked as delivered and delivery-status updates are sent to the appropriate senders.
Presence is maintained using an in-memory ConcurrentHashMap.
The tracker maintains the number of active sessions for each user.
This allows Chatter to correctly handle multiple browser tabs or devices:
User A
βββ Browser Tab 1
βββ Browser Tab 2
βββ Browser Tab 3
Active sessions = 3
User = ONLINE
The user becomes offline only after their last active WebSocket session disconnects.
The last-seen timestamp is persisted in MySQL when the user goes offline.
The backend contains a Spring Boot application test setup.
Run the Maven test suite with:
./mvnw testor:
mvn test- π Browser notifications for new messages
- π’ More detailed presence and last-seen indicators
- βοΈ Message editing
- ποΈ Message deletion
- β©οΈ Message replies
- π Message search
- π Message pinning
- π Emoji picker and reactions
- π₯ Group conversations
- π Audio/video calling
- βοΈ Object storage for media files
- π Better test coverage
- π Swagger/OpenAPI documentation
- π Production-ready Keycloak configuration
- π Monitoring with Prometheus and Grafana
- π³ Production container hardening
- βΈοΈ Kubernetes deployment
Contributions, suggestions, and improvements are welcome.
- Fork the repository
- Create a feature branch
- Commit your changes
- Push the branch
- Open a Pull Request
This project is licensed under the MIT License.