A full-featured cinema ticket reservation system built with Java 21, Spring Boot, and vanilla JavaScript. Browse screenings, pick your seats on an interactive seat map, and reserve β all with real-time availability, dynamic pricing, and concurrency protection.
- Interactive seat map β CSS Grid layout with color-coded seat types (Accessible, Standard, Premium)
- 15-minute seat locking β selected seats are held while you complete your booking
- Lazy lock expiry β no background jobs; expired locks are detected on read
- Full lifecycle β
PENDING β CONFIRMED β CANCELLEDorEXPIRED
| Occupancy | Tier | Multiplier |
|---|---|---|
| < 50% | π’ BASE | Γ1.00 |
| 50β79% | π‘ MID | Γ1.25 |
| β₯ 80% | π΄ HIGH | Γ1.50 |
Prices are locked at reservation time β no surprises on confirmation.
- Pessimistic locking on seat selection prevents double-booking
- Pessimistic locking on user row prevents active reservation count races
- Adjacent seat and same-row enforcement for group bookings
- Max 10 seats per reservation
- Max 3 active reservations per customer
- All seats must be adjacent in the same row
- Booking closes 30 minutes before screening starts
- Payment modeled as status:
PENDING β PAID β REFUNDED
- Java 21+
- Maven 3.8+
That's it β no Docker, no external database, no npm.
mvn spring-boot:run| Endpoint | URL |
|---|---|
| π¬ App | http://localhost:8080 |
| π Swagger UI | http://localhost:8080/swagger-ui.html |
| ποΈ H2 Console | http://localhost:8080/h2-console |
The app seeds reference data on startup:
| Type | Details |
|---|---|
| π€ Admin | User ID 1 |
| π₯ Customers | User IDs 2, 3, 4 |
| π₯ Movies | Inception, The Dark Knight, Interstellar |
| ποΈ Hall | Hall A (10 rows Γ 12 seats) |
| π Screenings | 3 screenings with different times |
mvn clean test| Suite | Count | Description |
|---|---|---|
| Unit tests | 44 | Service logic with Mockito |
| Integration tests | 44 | MockMvc end-to-end with @SpringBootTest |
| π₯ BDD scenarios | 58 | Cucumber feature files covering 12 user stories + 1 full-lifecycle scenario |
| Total | 146 | All passing β |
mvn test -Dtest=ReservationServiceTestCucumber scenarios live under src/test/resources/features/:
| Feature | User Story | Scenarios |
|---|---|---|
us003-seat-map.feature |
View seat map | 5 |
us004-initiate-reservation.feature |
Initiate reservation | 9 |
us005-confirm-reservation.feature |
Confirm reservation | 4 |
us006-auto-lock-expiry.feature |
Automatic lock expiry | 5 |
us007-cancel-reservation.feature |
Cancel reservation | 5 |
us008-view-reservations.feature |
View my reservations | 4 |
us009-user-identification.feature |
User identification | 4 |
us013-concurrent-booking.feature |
Concurrent booking | 2 |
us017-booking-cutoff.feature |
Booking cutoff | 4 |
us018-adjacent-seat-enforcement.feature |
Adjacent seats | 5 |
us019-dynamic-pricing.feature |
Dynamic pricing | 5 |
us020-active-reservation-limit.feature |
Reservation limit | 4 |
full-lifecycle.feature |
End-to-end lifecycle | 2 |
After running tests, check target/cucumber-reports.html for a formatted BDD report.
| Layer | Technology |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 3.3.11 |
| ORM | Spring Data JPA / Hibernate |
| Database | H2 (file-based, persists across restarts) |
| API Docs | Springdoc OpenAPI 2.5.0 |
| Frontend | Vanilla JS + HTML5 + CSS3 (single file) |
| Testing | JUnit 5, Mockito, MockMvc, Cucumber 7.18 |
| Code Gen | Lombok |
com.cinemabooking
βββ π¬ movie/ # Movie entity + repository
βββ ποΈ hall/ # Hall, Seat entities + repositories
βββ π
screening/ # Screening, ShowSeat, controller, service, DTOs
βββ ποΈ reservation/ # Reservation, ReservationSeat, controller, service, DTOs
βββ π€ user/ # User entity + repository, UserRole enum
βββ π§ shared/ # Enums, ErrorResponse, GlobalExceptionHandler, DataSeeder, PricingCalculator
Movie ββ1:Nββ Screening ββ1:Nββ ShowSeat ββN:1ββ Seat ββN:1ββ Hall
β
βββ1:Nββ Reservation ββ1:Nββ ReservationSeat ββN:1ββ ShowSeat
β
βββN:1ββ User
- ποΈ No external infrastructure β H2 file DB, no Redis, no message queues
- β° Lazy expiry β no scheduled jobs;
ShowSeat.getEffectiveStatus()checkslockedUntil - π Pessimistic locking β
@Lock(PESSIMISTIC_WRITE)on seat and user queries - π΅ Price snapshots β prices calculated at initiation, stored in
ReservationSeat - π Spec-first API β contract defined in
openapi.yaml, no annotations on Java code - π§Ή No auth framework β identity via
X-User-Idheader (training project focus is business logic)
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/screenings/{id}/seats |
πΊοΈ Seat map with pricing & availability |
POST |
/api/reservations |
ποΈ Initiate reservation (lock seats) |
POST |
/api/reservations/{code}/confirm |
β Confirm reservation |
POST |
/api/reservations/{code}/cancel |
β Cancel reservation |
GET |
/api/reservations |
π List user's reservations |
All mutation endpoints require the X-User-Id header.
Single-file app at src/main/resources/static/index.html:
- π Dark cinema theme with CSS custom properties
- πͺ Interactive seat grid β click to select, color-coded by type and status
- π΅ Live pricing β pricing tier badge + per-type price table
- β±οΈ Countdown timer β shows remaining lock time on pending reservations
- π± Responsive β breakpoints at 700px and 480px
- π My Reservations tab β filter by status, cancel confirmed bookings
| File | Description |
|---|---|
prd.md |
Product requirements & user stories |
tech-stack.md |
Technology choices & constraints |
decisions.md |
Architectural decision log |
CLAUDE.md |
AI assistant instructions |
testing-rules.md |
Test conventions & patterns |
This is a training/demo project. No license specified.