Skip to content

A real-time chat backend built with Spring Boot 3.5 and Redis — leveraging Pub/Sub for live message broadcasting, Redis Lists for persistent history, and a clean REST API testable via Postman.

Notifications You must be signed in to change notification settings

shendeyogesh11/Chat-application

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

💬 Redis Chat Application

A real-time chat backend built with Spring Boot 3.5 and Redis — leveraging Pub/Sub for live message broadcasting, Redis Lists for persistent history, and a clean REST API testable via Postman.

Java Spring Boot Redis Maven Tests


📋 Table of Contents


🌐 Overview

This is a backend-only REST API for a real-time chat application. It uses Redis as the sole data store, mapping every concept (rooms, participants, messages) to purpose-built Redis data structures. Real-time message broadcasting is powered by Redis Pub/Sub via a wildcard PatternTopic subscription (chatroom.topic.*), so a single subscriber handles all rooms.

There is no UI — the API is fully testable with Postman.


✨ Features

Feature Implementation Detail
🏠 Chat Rooms Created and stored as Redis Hashes (chatroom:{roomId}:meta)
👥 Participants Tracked per room in Redis Sets (chatroom:{roomId}:users)
📜 Message History Stored in Redis Lists (chatroom:{roomId}:messages) with configurable limit (default: 20)
Real-Time Messaging Published to chatroom.topic.{roomId} channel; received by RedisMessageSubscriber
🔑 Unique Room IDs roomId equals roomName — duplicate names throw a 400 Bad Request
🗑️ Room Deletion Atomically removes all 3 Redis keys for a room in a single DEL command
🛡️ Error Handling @RestControllerAdvice handles IllegalArgumentException (400) and generic exceptions (500)
🧪 TDD 9 passing tests covering service logic, Pub/Sub verification, and controller endpoints

🛠️ Tech Stack

Layer Technology
Language Java 17
Framework Spring Boot 3.5.10
Data Store Redis (via Lettuce client)
Redis Serialization StringRedisSerializer (keys) + GenericJackson2JsonRedisSerializer (values)
Pub/Sub RedisMessageListenerContainer + PatternTopic
Boilerplate Reduction Lombok (@Data, @RequiredArgsConstructor)
DTOs Java Records (CreateRoomRequest, JoinRequest, SendMessageRequest)
Build Tool Maven 3.9.12 (Wrapper included)
Testing JUnit 5 + Mockito (@WebMvcTest, @ExtendWith(MockitoExtension.class))
IDE IntelliJ IDEA
OS Windows 11 (PowerShell)

✅ Prerequisites

1. Java 17+

java -version

2. Redis Server running on localhost:6379

Windows Users: Redis may be running as a background Windows Service even if redis-cli isn't on your PATH. Verify from the install directory:

cd "C:\Program Files\Redis"
.\redis-cli.exe ping

Expected output: PONG

No authentication or non-default Redis configuration is needed — the app connects to localhost:6379 with no password.


🚀 Getting Started

Clone the Repository

git clone https://github.com/shendeyogesh11/chat-application.git
cd chat-application

Run the Application

Open PowerShell (or any terminal) in the project root — the directory containing pom.xml — and run:

.\mvnw spring-boot:run

Wait for the startup confirmation:

Started ChatappApplication in X.XXX seconds (JVM running for X.XXX)

The server is now live at http://localhost:8080.

Stop the Application

Press Ctrl + C in the terminal. Type Y if prompted to terminate the batch job.


🧪 Running Tests

The project follows Test-Driven Development (TDD). The suite has 9 tests across three test classes:

Test Class Type Tests Covered
ChatServiceTest Unit (Mockito) Create room success, duplicate room error, join room success, join non-existent room error, send message with Pub/Sub verification
ChatControllerTest Slice (@WebMvcTest) Create room API, join room API, retrieve chat history API
ChatappApplicationTests Integration Spring context loads

Run all tests with:

.\mvnw test

Expected output:

[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

Note: ChatappApplicationTests (context load) requires Redis to be running. All service and controller tests use Mockito mocks and do not require a live Redis instance.


📖 API Documentation

Base URL: http://localhost:8080/api/chatapp/chatrooms


1. Create a Chat Room

Creates a new room. The roomId returned is identical to the roomName provided.

POST /api/chatapp/chatrooms

Request Body:

{
  "roomName": "general"
}

Response 200 OK:

{
  "message": "Chat room 'general' created successfully.",
  "roomId": "general",
  "status": "success"
}

Error — Duplicate name 400 Bad Request:

{
  "error": "Chat room already exists: general",
  "status": "error"
}

2. Join a Chat Room

Adds a participant to an existing room's Redis Set. Joining is idempotent — adding the same participant twice has no side effects (Redis Set semantics).

POST /api/chatapp/chatrooms/{roomId}/join

Example: POST /api/chatapp/chatrooms/general/join

Request Body:

{
  "participant": "guest_user"
}

Response 200 OK:

{
  "message": "User 'guest_user' joined chat room 'general'.",
  "status": "success"
}

Error — Room not found 400 Bad Request:

{
  "error": "Room not found: general",
  "status": "error"
}

3. Send a Message

Sends a message to a room. The timestamp is set server-side using Instant.now() (ISO-8601). The message is both persisted to a Redis List and published to the room's Pub/Sub channel.

POST /api/chatapp/chatrooms/{roomId}/messages

Example: POST /api/chatapp/chatrooms/general/messages

Request Body:

{
  "participant": "guest_user",
  "message": "Hello, everyone!"
}

Response 200 OK:

{
  "message": "Message sent successfully.",
  "status": "success"
}

Real-Time Verification: After sending, check the application console for the Pub/Sub broadcast log:

[REAL-TIME EVENT]
 Room: chatroom.topic.general
 User: guest_user
 Said: "Hello, everyone!"
 Timestamp: 2024-01-01T10:00:00.000000000Z

Error — Room not found 400 Bad Request:

{
  "error": "Room not found: general",
  "status": "error"
}

4. Retrieve Chat History

Returns the last N messages from a room in chronological order. Defaults to the last 20 messages if limit is omitted or <= 0.

GET /api/chatapp/chatrooms/{roomId}/messages?limit={n}

Example: GET /api/chatapp/chatrooms/general/messages?limit=10

Response 200 OK:

{
  "messages": [
    {
      "participant": "guest_user",
      "message": "Hello, everyone!",
      "timestamp": "2024-01-01T10:00:00.000000000Z"
    },
    {
      "participant": "another_user",
      "message": "Hi, guest_user!",
      "timestamp": "2024-01-01T10:01:00.000000000Z"
    }
  ]
}

5. Delete a Chat Room (Optional)

Permanently removes all 3 Redis keys for a room (meta, users, messages) in a single atomic DEL command.

DELETE /api/chatapp/chatrooms/{roomId}

Example: DELETE /api/chatapp/chatrooms/general

Response 200 OK:

{
  "message": "Chat room 'general' deleted successfully.",
  "status": "success"
}

Quick Reference

Method Endpoint Description
POST /api/chatapp/chatrooms Create a chat room
POST /api/chatapp/chatrooms/{roomId}/join Join a chat room
POST /api/chatapp/chatrooms/{roomId}/messages Send a message
GET /api/chatapp/chatrooms/{roomId}/messages?limit=N Retrieve chat history (default limit: 20)
DELETE /api/chatapp/chatrooms/{roomId} Delete a chat room

📂 Project Structure

chat-application/
├── pom.xml
├── mvnw / mvnw.cmd                              # Maven wrapper scripts
└── src/
    ├── main/
    │   ├── java/com/backend/assignment/chatapp/
    │   │   ├── ChatappApplication.java              # @SpringBootApplication entry point
    │   │   ├── config/
    │   │   │   └── RedisConfig.java                 # RedisTemplate (JSON serialization) +
    │   │   │                                        # Pub/Sub container (PatternTopic: chatroom.topic.*)
    │   │   ├── controller/
    │   │   │   └── ChatController.java              # @RestController — 5 REST endpoints
    │   │   ├── dto/
    │   │   │   ├── CreateRoomRequest.java           # record(String roomName)
    │   │   │   ├── JoinRequest.java                 # record(String participant)
    │   │   │   └── SendMessageRequest.java          # record(String participant, String message)
    │   │   ├── exception/
    │   │   │   └── GlobalExceptionHandler.java      # @RestControllerAdvice — maps exceptions to HTTP status
    │   │   ├── model/
    │   │   │   ├── ChatMessage.java                 # @Data — participant, message, timestamp (ISO-8601)
    │   │   │   └── ChatRoom.java                    # @Data — roomId, name
    │   │   ├── repository/
    │   │   │   └── ChatRepository.java              # All Redis Hash / Set / List operations
    │   │   └── service/
    │   │       ├── ChatService.java                 # Business logic + Pub/Sub publish
    │   │       └── RedisMessageSubscriber.java      # MessageListener — deserializes & logs real-time events
    │   └── resources/
    │       └── application.properties               # Redis host, port, Lettuce pool config
    └── test/
        └── java/com/backend/assignment/chatapp/
            ├── ChatappApplicationTests.java          # Spring context load test
            ├── ChatControllerTest.java               # @WebMvcTest — MockMvc endpoint assertions
            └── ChatServiceTest.java                  # Mockito unit tests — business logic & Pub/Sub

🗄️ Redis Data Model

Every operation maps to a specific Redis primitive. Keys are namespaced by {roomId}.

Concept Redis Type Exact Key Content
Room Metadata Hash chatroom:{roomId}:meta Fields: id → roomId, name → roomName
Participants Set chatroom:{roomId}:users Unique participant name strings
Messages List chatroom:{roomId}:messages JSON-serialized ChatMessage objects (appended via RPUSH)
Real-Time Events Pub/Sub chatroom.topic.{roomId} JSON-serialized ChatMessage broadcasts

Room existence check: redisTemplate.hasKey("chatroom:{roomId}:meta")

Message format stored in Redis List:

{
  "@class": "com.backend.assignment.chatapp.model.ChatMessage",
  "participant": "guest_user",
  "message": "Hello, everyone!",
  "timestamp": "2024-01-01T10:00:00.000000000Z"
}

Values are serialized using GenericJackson2JsonRedisSerializer, which embeds type metadata (@class). Keys are plain strings via StringRedisSerializer and are fully readable in redis-cli.


⚡ Real-Time Messaging Flow

When POST /chatrooms/{roomId}/messages is called:

ChatController
     │
     ▼
ChatService.sendMessage()
     │
     ├── 1. Sets timestamp server-side: Instant.now().toString()
     │
     ├── 2. chatRepository.saveMessage()
     │       └── RPUSH chatroom:{roomId}:messages  →  Redis List  (persistence)
     │
     └── 3. redisTemplate.convertAndSend("chatroom.topic.{roomId}", message)
                 └── Redis Pub/Sub publish
                           │
                           ▼
              RedisMessageListenerContainer
              (PatternTopic: "chatroom.topic.*")
                           │
                           ▼
              RedisMessageSubscriber.onMessage()
                  └── Deserializes JSON  →  ChatMessage
                  └── Logs [REAL-TIME EVENT] to application console

In a production system, RedisMessageSubscriber.onMessage() would push events to connected WebSocket clients or Server-Sent Events (SSE) streams instead of logging to the console.


🛡️ Error Handling

All exceptions are caught by GlobalExceptionHandler (@RestControllerAdvice) and return a consistent JSON envelope.

Scenario Exception HTTP Status Example Response
Duplicate room name IllegalArgumentException 400 Bad Request { "error": "Chat room already exists: general", "status": "error" }
Room not found (any operation) IllegalArgumentException 400 Bad Request { "error": "Room not found: general", "status": "error" }
Unexpected server error Exception 500 Internal Server Error { "error": "An unexpected error occurred.", "details": "..." }

All business rule violations use IllegalArgumentException and map to 400 Bad Request. The application does not use 404 Not Found in the current implementation.


⚙️ Configuration

src/main/resources/application.properties:

spring.application.name=chatapp

# Redis Connection
spring.data.redis.host=localhost
spring.data.redis.port=6379

# Lettuce Connection Pool (handles concurrent requests)
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0
spring.data.redis.lettuce.pool.max-wait=-1ms

To connect to a remote Redis instance, update host and port. To enable Redis persistence, configure RDB snapshots or AOF in your redis.conf.


© 2026 Yogesh Shende. All Rights Reserved.

About

A real-time chat backend built with Spring Boot 3.5 and Redis — leveraging Pub/Sub for live message broadcasting, Redis Lists for persistent history, and a clean REST API testable via Postman.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages