SeatFlow is a production-style Cinema Seat Reservation System backend built with Java 21 and Spring Boot 3. The core engineering problem is safe concurrent seat reservation: two users must never be able to reserve the same seat for the same showtime.
The project is intentionally a layered monolith, not a microservice demo. It focuses on clean backend architecture, transactional business rules, PostgreSQL constraints, JWT security, Dockerized local setup, and tests around the critical reservation flow.
- Java 21
- Spring Boot 3
- Maven
- PostgreSQL
- Spring Data JPA
- Spring Security + JWT
- Flyway migrations
- Redis-backed Spring Cache
- Swagger/OpenAPI
- JUnit 5, Mockito, Testcontainers
- Docker + Docker Compose
- Lombok
src/main/java/com/seatflow
├── config # Security, OpenAPI, application properties, bootstrap config
├── controller # REST endpoints and HTTP response codes
├── dto # Request/response contracts
├── entity # JPA persistence model
├── exception # Global exception handling and domain exceptions
├── mapper # Entity-to-DTO conversion
├── repository # Spring Data JPA access
├── security # JWT, principal, user details, auth filter
├── service # Transactional business logic
├── util # Small reusable helpers
└── validation # Custom Bean Validation constraints
Seat reservation is protected by two layers:
ReservationService.reserve(...)runs in a transaction and locks requestedSeatrows withPESSIMISTIC_WRITE.- PostgreSQL enforces a partial unique index:
create unique index ux_active_showtime_seat
on reservation_seats(showtime_id, seat_id)
where active = true;Pending and confirmed reservations keep reservation_seats.active = true. Expired or cancelled reservations release their seats by setting active = false. This means the database rejects double-booking even if two application requests race.
- User registration and login
- BCrypt password hashing
- JWT authentication
- USER and ADMIN roles
- Movie management
- Cinema room creation with generated seat layout
- Showtime scheduling with overlap prevention
- Seat reservation with expiration holds
- Mock payment approval/failure flow
- Admin reservation search
- Admin occupancy statistics
- Pagination, filtering, sorting, validation, and standardized API responses
- Swagger UI
- Dockerized PostgreSQL, Redis, and API service
Create a local environment file:
cp .env.example .envStart the full stack:
docker-compose up --buildThe API will be available at:
- API base URL:
http://localhost:8080/api/v1 - Swagger UI:
http://localhost:8080/swagger-ui.html - Health check:
http://localhost:8080/actuator/health
The Docker Compose setup bootstraps an admin account from environment variables:
ADMIN_EMAIL=admin@seatflow.local
ADMIN_PASSWORD=change-this-admin-password
Change these values before using the project outside local development.
Start PostgreSQL and Redis, then provide the same variables from .env.example.
mvn spring-boot:runRun the test suite:
mvn testThe integration test uses Testcontainers and requires Docker.
Register a user:
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"fullName": "Ada Lovelace",
"email": "ada@example.com",
"password": "strong-password"
}'Login:
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "ada@example.com",
"password": "strong-password"
}'Create a movie as admin:
curl -X POST http://localhost:8080/api/v1/movies \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"title": "Interstellar",
"genre": "Sci-Fi",
"durationMinutes": 169,
"rating": "PG-13",
"synopsis": "A team travels through a wormhole in search of a future for humanity.",
"active": true
}'Create a room with generated seats:
curl -X POST http://localhost:8080/api/v1/rooms \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "Room A",
"rowsCount": 8,
"seatsPerRow": 12
}'Create a showtime:
curl -X POST http://localhost:8080/api/v1/showtimes \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"movieId": 1,
"roomId": 1,
"startsAt": "2026-09-01T20:00:00Z",
"ticketPrice": 12.50
}'Reserve seats:
curl -X POST http://localhost:8080/api/v1/reservations \
-H "Authorization: Bearer <USER_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"showtimeId": 1,
"seatIds": [1, 2, 3]
}'Pay for a reservation:
curl -X POST http://localhost:8080/api/v1/reservations/1/payments \
-H "Authorization: Bearer <USER_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"paymentToken": "tok_mock_approved",
"simulateFailure": false
}'View occupancy statistics as admin:
curl "http://localhost:8080/api/v1/admin/occupancy?showtimeId=1" \
-H "Authorization: Bearer <ADMIN_TOKEN>"- All request bodies are validated with Bean Validation.
- Responses use a consistent
ApiResponse<T>wrapper. - Validation errors include field-level details.
- List endpoints support Spring pageable parameters such as
page,size, andsort. - Business conflicts return
409 Conflict, missing resources return404 Not Found, and unauthorized requests return401 Unauthorized.
Flyway owns schema creation in src/main/resources/db/migration.
Important modeling choices:
- Users and roles are normalized through
user_roles. - Seats belong to a cinema room and are generated when a room is created.
- Showtimes reference a movie and room, with an overlap check in the service layer.
- Reservations own reservation-seat rows.
- Active reservation seats are protected by a PostgreSQL partial unique index.
- Payments are one-to-one with reservations.
The project includes:
- Mockito service tests for payment edge cases.
- A PostgreSQL/Testcontainers integration test that launches concurrent reservation attempts for the same seat and verifies only one succeeds.
mvn testAdd screenshots here after running locally:
- Swagger UI
- Successful login response
- Seat reservation response
- Admin occupancy statistics
- Test run output
- Add refresh tokens and token revocation.
- Add cancellation endpoint with refund policy simulation.
- Add richer reporting by movie, room, and date range.
- Add rate limiting for authentication endpoints.
Esta guia asume que vas a ejecutar SeatFlow con Docker, que es la forma recomendada porque levanta la API, PostgreSQL y Redis con un solo comando.
Instala y abre Docker Desktop:
- Docker Desktop para Windows, macOS o Linux.
- Git, si vas a clonar el repositorio.
Verifica que Docker este funcionando:
docker --version
docker compose versionEn Windows PowerShell:
cd C:\Users\emica\Documents\Practicas\Web\SeatFlowSi clonaste el repositorio en otra ruta, entra a la carpeta donde lo descargaste.
Copia el archivo de ejemplo:
Copy-Item .env.example .envEn Linux/macOS:
cp .env.example .envPuedes dejar los valores por defecto para desarrollo local. Antes de usar el proyecto en un entorno real, cambia especialmente:
JWT_SECRET
POSTGRES_PASSWORD
ADMIN_PASSWORD
Ejecuta:
docker compose up --buildLa primera vez puede tardar porque Docker debe descargar imagenes y Maven debe descargar dependencias.
Cuando el servicio termine de iniciar, la API estara disponible en:
http://localhost:8080
En el navegador abre:
http://localhost:8080/swagger-ui.html
Desde Swagger puedes probar todos los endpoints sin escribir curl manualmente.
Usa el endpoint:
POST /api/v1/auth/login
Con las credenciales configuradas en .env:
{
"email": "admin@seatflow.local",
"password": "change-this-admin-password"
}Copia el valor de accessToken de la respuesta.
En Swagger, presiona el boton Authorize y escribe:
Bearer TU_ACCESS_TOKEN
Reemplaza TU_ACCESS_TOKEN por el token real.
Como administrador:
- Crea una pelicula con
POST /api/v1/movies. - Crea una sala con
POST /api/v1/rooms. - Crea una funcion con
POST /api/v1/showtimes.
Como usuario:
- Registra un usuario con
POST /api/v1/auth/register. - Inicia sesion con
POST /api/v1/auth/login. - Reserva asientos con
POST /api/v1/reservations. - Paga la reserva con
POST /api/v1/reservations/{reservationId}/payments.
Presiona Ctrl + C en la terminal donde esta corriendo Docker Compose.
Para detener y eliminar los contenedores:
docker compose downPara eliminar tambien los datos de PostgreSQL guardados en el volumen local:
docker compose down -vSi tienes Maven instalado:
mvn testSi no tienes Maven instalado, puedes usar Docker:
docker run --rm -v "$(pwd):/workspace" -w /workspace maven:3.9.9-eclipse-temurin-21 mvn testEn Windows PowerShell:
docker run --rm -v "${PWD}:/workspace" -w /workspace maven:3.9.9-eclipse-temurin-21 mvn testLas pruebas de integracion usan Testcontainers, asi que Docker debe estar abierto.
Si el puerto 8080 ya esta ocupado, cambia el mapeo del servicio app en docker-compose.yml.
Si ves un error relacionado con PKIX path building failed, normalmente es un problema de certificados o proxy al descargar dependencias de Maven desde Docker.
Si PostgreSQL no inicia correctamente, reinicia los contenedores:
docker compose down
docker compose up --build