Build a Customer Support Ticket System that allows users to raise support tickets and enables support agents to manage them effectively. The system should support ticket creation, assignment, status updates, commenting, and closing of tickets.
- Java (17+)
- Spring Boot (3.0+)
- Spring Data JPA
- PostgreSQL (for production)
- H2 Database (for testing)
- Gradle
- Postman (for API testing)
1. Ticket Creation: Customers can create support tickets with details such as title, description.
- The request is recorded with the provided title and description
- The ticket is automatically marked as OPEN
- A support agent is automatically assigned
- The system returns the created ticket details
- Create Ticket API Endpoint:
POST /api/ticketsAuthorization: Bearer <JWT_TOKEN>{
"title": "Unable to login",
"description": "I am getting an error while logging into my account"
}HTTP Status: 201 CREATED
{
"success": true,
"message": "Ticket created successfully",
"data": {
"id": "0bb729e9-a932-4564-8f18-96aad738f453",
"title": "Unable to login",
"description": "I am getting an error while logging into my account",
"status": "OPEN",
"assignedToName": "Support Agent Name",
"createdAt": "2026-02-18T10:15:30"
}
}2. Assign Ticket Feature: This feature allows a support agent to assign an existing ticket to another support agent.
POST /api/tickets/{ticketId}/assign
Authorization: Bearer <JWT_TOKEN>-
Make sure the ticket already exists in the system.
-
Ensure both users exist and have role
SUPPORT_AGENT. -
Send a POST request with the IDs of:
- User assigning the ticket
- User receiving the ticket
{
"assignedToUserId": "74379c78-6c36-49fc-bd9d-be64a92ddc3d",
"assignedByUserId": "b71db4fd-38c7-4b01-b5f3-c28c81194b91"
}{
"success": true,
"message": "Ticket assigned successfully",
"data": {
"id": "445ba19f-ac66-4048-aff9-d05363d648b5",
"ticketId": "610a571b-380d-43f4-8b93-9816c9c9bb15",
"assignedToUserId": "74379c78-6c36-49fc-bd9d-be64a92ddc3d",
"assignedByUserId": "b71db4fd-38c7-4b01-b5f3-c28c81194b91"
}
}The assignment will fail if:
- Ticket does not exist
- Ticket status is
CLOSED - Either user does not exist
- Either user is not a
SUPPORT_AGENT - A user tries to assign the ticket to themselves
curl -X POST \
"http://localhost:8080/api/tickets/{ticketId}/assign" \
-H "Content-Type: application/json" \
-d '{
"assignedToUserId": "<support-agent-id>",
"assignedByUserId": "<assigning-agent-id>"
}'NOTE: Replace {ticketId} and user IDs with valid UUIDs present in your database.
3. View Ticket By ID
- View Ticket (For Support Agent User) API Endpoint:
GET /api/tickets/{id}?role=agentAuthorization: Bearer <JWT_TOKEN>- Successful Response Example HTTP Status: 200 SUCCESS
{
"success": true,
"message": "Ticket fetched successfully",
"data": {
"title": "Unable to reset password",
"description": "Customer reports that the password reset link expires immediately after clicking it.",
"createdAt": "2026-02-17T09:15:00",
"priority": "HIGH",
"status": "OPEN"
}
}- View Ticket (For Customer User) API Endpoint:
GET /api/tickets/{id}?role=customerAuthorization: Bearer <JWT_TOKEN>- Successful Response Example HTTP Status: 200 SUCCESS
{
"success": true,
"message": "Ticket fetched successfully",
"data": {
"title": "Unable to reset password",
"description": "Customer reports that the password reset link expires immediately after clicking it.",
"status": "OPEN",
"createdAt": "2026-02-17T09:15:00",
"agentName": "Bob Johnson"
}
}4. Add Comment to Ticket: This feature allows authorized users to add comments to an existing ticket. Only the ticket creator or the assigned agent can add comments to that ticket.
- Validate request body.
- Fetch User by ID.
- Fetch Ticket by ID.
- Check if user belongs to the ticket.
- Create and save Comment.
- Return response with comment
id,body, andcreatedAt.
POST /api/tickets/{ticketId}/comments
Authorization: Bearer <JWT_TOKEN>
{
"body": "i have a issue"
}{
"success": true,
"message": "Comment added successfully",
"data": {
"id": "ea6aa95a-bee0-46c4-b63f-4641f5750dce",
"body": "i have a issue",
"createdAt": "2026-02-18T12:21:12.895856"
}
}5. View All Comments Of a Ticket: Retrieve all comments associated with a specific ticket.
- Access is granted only if:
- The user is the ticket creator, OR
- The user is the assigned agent
GET /api/tickets/{ticketId}/commentsAuthorization: Bearer <JWT_TOKEN>{
"success": true,
"message": "Comments retrieved successfully",
"data": [
{
"body": "Issue resolved",
"commenter": "Jatin",
"createdAt": "2026-02-20T10:15:30"
}
]
}6. Register User (Customer only)
Allows new customers to create an account within the system without authentication configuration.
Endpoint: POST /api/auth/register
Request Body:
{
"name": "Jatin Joshi",
"email": "jatin@gmail.com",
"password": "Password@123"
}
Constraints & Validations:
- Name: Required, max 50 characters, must contain letters (cannot be purely numeric).
- Email: Required, must be a valid email format, max 100 characters.
- Password: Required, 8-16 characters. Must include at least:
- One uppercase letter
- One lowercase letter
- One digit
- One special character (
@#$%^&+=!)
Success Response: 201 Created
{
"success": true,
"message": "User created successfully",
"data": {
"name": "Jatin Joshi",
"email": "jatin@gmail.com",
"id": "501a4912-4351-428b-a4ed-971e28ee1086"
}
}
Error Responses:
400 Bad Request: Validation failed (e.g., weak password or invalid email).409 Conflict: User with the provided email already exists.
- UserControllerTest: Validates API contract, HTTP status codes, and JSON response structure.
- UserServiceTest: Validates business logic, password encoding, and duplicate email prevention.
Error Responses:
400 Bad Request: Validation failed (e.g., weak password or invalid email).409 Conflict: User with the provided email already exists.
Allows new customers to login the system with proper authentication.
Endpoint: POST /api/auth/login
Request Body:
{
"email": "Priyansh@gmail.com",
"password": "Password@123"
}
Constraints & Validations:
- Email: Required
- Password: Required
Success Response:
200 OK
{
"success": true,
"message": "Login successful",
"data": {
"token": "<JWT_TOKEN>",
"userId": "UUID",
"email": "saxena@gmail.com",
"role": "CUSTOMER"
}
}Error Responses:
401 Unauthorized: Invalid email or password.
{
"code": "INVALID_CREDENTIALS",
"message": "Invalid email or password"
}
8. Swagger OpenApi documentation
This project uses SpringDoc OpenAPI to automatically generate interactive documentation for all REST endpoints. This allows you to visualize, explore, and test the API directly from your browser.
Once the application is running, you can access the documentation at the following URLs:
- Interactive UI (Swagger UI): http://localhost:8080/swagger-ui/index.html
- OpenAPI Specification (JSON): http://localhost:8080/v3/api-docs
- Open the Swagger UI link above.
- Click on any endpoint (e.g.,
POST /api/auth/register). - Click the "Try it out" button.
- The request body will be pre-filled with example data defined in our
@Schemaannotations. - Edit the data if needed and click "Execute" to see the real response from the server.
7. Update Ticket (Customer & Support Agent)
Allows a CUSTOMER or SUPPORT_AGENT to update an existing ticket based on role-specific permissions.
Endpoint: PATCH /api/tickets/{ticketId}
Headers:
User-Id: <UUID>
Role-Based Update Rules
CUSTOMER
- Can update description
- Can update status (e.g.,
CLOSED) - Cannot update priority
- Cannot update ticket once it is already
CLOSED
SUPPORT_AGENT
-
Can update status
-
Can update priority
-
Must be assigned to the ticket, else cannot update
-
Cannot update ticket once it is already
CLOSED -
Must provide at least one of:
statuspriority
-
Can only change ticket status:
- From
OPEN→IN_PROGRESSorCLOSED - From
IN_PROGRESS→CLOSED
- From
Request Examples
Customer Request
{
"description": "Updated description",
"status": "CLOSED"
}Agent Request
{
"priority": "LOW",
"status": "CLOSED"
}Constraints & Validations
Common Validations
ticketIdmust be a valid UUID.- Ticket must exist.
- Ticket must not already be
CLOSED. - Enum fields (
status,priority) must contain valid values.
Customer-Specific
- Cannot update
priority. - Can only update their own ticket.
Agent-Specific
-
Must be assigned to the ticket.
-
At least one of
statusorprioritymust be provided. -
Status transitions allowed:
OPEN→IN_PROGRESSorCLOSEDIN_PROGRESS→CLOSED
Success Response: 200 OK
Customer Response
{
"success": true,
"message": "Ticket updated successfully",
"data": {
"title": "Improve ticket search performance",
"description": "Updated description",
"status": "CLOSED",
"createdAt": "2026-02-15T14:22:31",
"updatedAt": "2026-02-23T12:50:19.229691"
}
}Agent Response
{
"success": true,
"message": "Ticket updated successfully",
"data": {
"title": "Improve ticket search performance",
"description": "test123",
"status": "CLOSED",
"priority": "LOW",
"createdAt": "2026-02-15T14:22:31",
"updatedAt": "2026-02-23T12:50:19.229691"
}
}Error Responses
400 Bad Request
- Invalid enum value (
status/priority) - Agent request missing both
statusandpriority - Attempt to update restricted fields
- Ticket already
CLOSED
Example:
{
"code": "BAD_REQUEST",
"message": "At least one of status or priority must be provided"
}403 Forbidden
- Agent attempting to update ticket not assigned to them
- Customer attempting to update another customer's ticket
404 Not Found
- Ticket does not exist
Service Layer Test Cases
- Customer successfully closing ticket
- Customer attempting to update priority
- Updating already
CLOSEDticket - Support agent updating assigned ticket
- Agent updating ticket not assigned to them
- Agent updating already
CLOSEDticket
Controller Layer Test Cases
- Successful update →
200 OK - Invalid enum value in request →
400 Bad Request - Agent unauthorized update →
403 Forbidden
Make sure you have these installed before starting:
- Java
- PostgreSQL
- pgAdmin
git clone https://github.com/Priyansh7999/customer-support-ticket-system.git
cd customer-support-ticket-systemDownload and install PostgreSQL from (https://www.postgresql.org/download/)
- Open pgAdmin
- Connect to your PostgreSQL server
- Create database name: customer_support_ticket_system
- Main
spring.application.name=customer-support-ticket-system
spring.datasource.url=jdbc:postgresql://localhost:5432/customer_support_ticket_system
spring.datasource.username=postgres
spring.datasource.password=<YOUR_DB_PASSWORD>
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.defer-datasource-initialization=true
management.endpoints.web.exposure.include=health
management.endpoint.health.show-details=always- Test
spring.application.name=customer-support-ticket-system
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true
./gradlew runhttp://localhost:8080