A RESTful booking application built with Spring Boot 3, enabling users to browse accommodations, make bookings, and process payments — with real-time Telegram notifications and Stripe integration.
- Overview
- Tech Stack
- Architecture
- Domain Models
- Features
- Getting Started
- Environment Variables
- API Reference
- Security
- Database Schema
- Notifications
- Tests
- Swagger-UI
Booking App is a backend REST API that allows:
- Customers to register, browse accommodations, create bookings, and pay via Stripe
- Managers to manage accommodations, amenities, and monitor all bookings and payments
- Admins to receive real-time Telegram notifications about new and canceled bookings
The API is documented with Swagger / OpenAPI and is accessible at /booking-api/swagger-ui.html when running.
| Category | Technology |
|---|---|
| Language | Java 17 |
| Framework | Spring Boot 3.2.4 |
| Security | Spring Security + JWT (jjwt 0.12.5) |
| Database | PostgreSQL |
| Migrations | Liquibase |
| ORM | Spring Data JPA / Hibernate |
| Mapping | MapStruct 1.5.5 |
| Payments | Stripe Java SDK 25.7.0 |
| Notifications | Telegram Bots (telegrambots 5.5.0) |
| API Docs | SpringDoc OpenAPI 2.2.0 |
| Validation | Hibernate Validator |
| Boilerplate | Lombok |
| Testing | JUnit 5, Testcontainers, Spring Security Test |
| Build | Maven + Checkstyle |
The application follows a standard layered architecture:
Controller → Service → Repository → Database
↓
Mapper (MapStruct)
↓
DTO Layer
A layered box diagram showing the architecture of the project with side APIs.

Key packages:
controller— REST endpoints (AccommodationController, AmenityController, AuthenticationController, BookingController, PaymentController)service— Business logic (withImplclasses)repository— Spring Data JPA repositories + Specification pattern for dynamic searchmodel— JPA entities (Accommodation, Amenity, Booking, Payment, User, Role)dto— Request/Response DTOs per domainmapper— MapStruct mapperssecurity— JWT filter, CustomUserDetailsService, AuthenticationServiceconfig— SecurityConfig, StripeConfig, TelegramBotConfig, OpenApiConfigclient— StripeClient, MyTelegramBotvalidation— Custom validators (Email, AccommodationType)exception— Global exception handler
| Field | Type | Notes |
|---|---|---|
| id | Long | Primary key |
| type | Enum | HOUSE, APARTMENT, CONDO, VACATION_HOME |
| location | String | Address / area |
| size | String | e.g. "2BR", "Studio" |
| dailyRate | BigDecimal | Price per night |
| availability | Integer | Number of units available |
| amenities | Set<Amenity> | Many-to-many |
| isDeleted | boolean | Soft delete |
| Field | Type | Notes |
|---|---|---|
| id | Long | Primary key |
| checkInDate | LocalDate | |
| checkOutDate | LocalDate | |
| accommodation | Accommodation | FK |
| user | User | FK |
| status | Enum | PENDING, CONFIRMED, CANCELED, EXPIRED |
| isDeleted | boolean | Soft delete |
| Field | Type | Notes |
|---|---|---|
| id | Long | Primary key |
| booking | Booking | FK |
| user | User | FK |
| sessionUrl | String | Stripe checkout URL |
| sessionId | String | Stripe session ID |
| amount | BigDecimal | |
| status | Enum | PENDING, PAID, EXPIRED, CANCELED |
| isDeleted | boolean | Soft delete |
| Field | Type |
|---|---|
| id | Long |
| String (unique) | |
| password | String (encoded) |
| firstName | String |
| lastName | String |
| roles | Set<Role> |
Roles: CUSTOMER, MANAGER
| Field | Type |
|---|---|
| id | Long |
| name | String |
| description | String |
- Create, read, update, and soft-delete accommodations (MANAGER only)
- List all accommodations with pagination
- Filter accommodations by amenity
- Dynamic search by type, location, daily rate range, and minimum availability
- Full CRUD for amenities (create/update/delete restricted to MANAGER)
- Publicly viewable
- Register with email + password (custom email validation)
- Login returns a signed JWT token
- JWT-based stateless authentication on all protected endpoints
- Customers can create, view, update, and delete their own bookings
- Booking creation automatically decrements accommodation availability
- Managers can query any user's bookings by user ID and status
- Managers can update booking status (PATCH)
- Expired booking detection via scheduled job
- Create a Stripe Checkout Session linked to a booking
- Redirect-based success and cancel flows
- Renew expired payment sessions
- View payment history (own for customers, any user for managers)
- Prevents booking if unpaid bookings exist (
UnpaidBookingException)
- Manager receives a Telegram message when a booking is created or canceled
- Configured via bot token, username, and admin chat ID
- Java 17+
- Maven 3.8+
- Docker & Docker Compose (for PostgreSQL)
- A Stripe account (test mode is fine)
- A Telegram bot token (via @BotFather)
git clone https://github.com/your-username/booking-app.git
cd booking-appCreate a .env file in the project root (see Environment Variables).
Start the database:
docker-compose up -dBuild and run:
mvn clean install
java -jar target/booking-app-0.0.1-SNAPSHOT.jarThe API will be available at: http://localhost:8080/booking-api
Swagger UI: http://localhost:8080/booking-api/swagger-ui.html
Create a .env file in the project root:
# Database
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/booking_db
SPRING_DATASOURCE_USERNAME=your_db_user
SPRING_DATASOURCE_PASSWORD=your_db_password
# JWT
JWT_SECRET_STRING=your_very_long_secret_key_here
# Stripe
STRIPE_API_KEY=sk_test_...
# Telegram
TELEGRAM_BOT_TOKEN=your_telegram_bot_token
TELEGRAM_BOT_USERNAME=your_bot_username
ADMIN_CHAT_ID=your_telegram_chat_idJWT tokens expire after
3,000,000milliseconds (~50 minutes) by default.
The base path for all endpoints is /booking-api.
You can call POST, PUT, PATCH, GET, DELETE requests using this application. All endpoints are listed in the following table:
| Action | Request Type | Path | RequestDTO | ResponseDTO | Required Authority |
|---|---|---|---|---|---|
| Register new User | POST | /booking-api/auth/register | RegistrationRequestDto | RegistrationResponseDto | PUBLIC |
| User Login | POST | /booking-api/auth/login | LoginRequestDto | LoginResponseDto | PUBLIC |
| Create new Accommodation | POST | /booking-api/accommodations | AccommodationRequestDto | AccommodationResponseDto | MANAGER |
| Get an Accommodation | GET | /booking-api/accommodations/{id} | EMPTY | AccommodationResponseDto | CUSTOMER |
| Get all Accommodations | GET | /booking-api/accommodations | EMPTY | List<AccommodationResponseDto> | CUSTOMER |
| Get Accommodations by AmenityId | GET | /booking-api/accommodations/amenity/{amenityId} | EMPTY | List<AccommodationWithoutAmenityIdsDto> | CUSTOMER |
| Search Accommodations | GET | /booking-api/accommodations/search | AccommodationSearchParameters | List<AccommodationResponseDto> | CUSTOMER |
| Update Accommodation | PUT | /booking-api/accommodations/{id} | AccommodationRequestDto | AccommodationResponseDto | MANAGER |
| Delete Accommodation | DELETE | /booking-api/accommodations/{id} | EMPTY | EMPTY | MANAGER |
| Create new Amenity | POST | /booking-api/amenities | AmenityRequestDto | AmenityResponseDto | MANAGER |
| Get an Amenity | GET | /booking-api/amenities/{id} | EMPTY | AmenityResponseDto | CUSTOMER |
| Get all Amenities | GET | /booking-api/amenities | EMPTY | List<AmenityResponseDto> | CUSTOMER |
| Update Amenity | PUT | /booking-api/amenities/{id} | AmenityRequestDto | AmenityResponseDto | MANAGER |
| Delete Amenity | DELETE | /booking-api/amenities/{id} | EMPTY | EMPTY | MANAGER |
| Create new Booking | POST | /booking-api/bookings | BookingRequestDto | BookingResponseDto | CUSTOMER |
| Get a Booking | GET | /booking-api/bookings/{id} | EMPTY | BookingResponseDto | CUSTOMER |
| Get my Bookings | GET | /booking-api/bookings/my | EMPTY | List<BookingResponseDto> | CUSTOMER |
| Update Booking | PUT | /booking-api/bookings/{id} | BookingRequestDto | BookingResponseDto | CUSTOMER |
| Get Bookings by Status and UserId | GET | /booking-api/bookings?user_id={id}&status={status} | EMPTY | List<BookingResponseDto> | MANAGER |
| Update Status of Booking | PATCH | /booking-api/bookings/{id} | UpdateStatusDto | EMPTY | MANAGER |
| Delete Booking | DELETE | /booking-api/bookings/{id} | EMPTY | EMPTY | CUSTOMER |
| Create new Payment | POST | /booking-api/payments | PaymentRequestDto | PaymentResponseDto | CUSTOMER |
| Successful Payment | PUT | /booking-api/payments/success?session_id={id} | EMPTY | PaymentResponseDto | CUSTOMER |
| Cancel Payment | PUT | /booking-api/payments/cancel?session_id={id} | EMPTY | String | CUSTOMER |
| Renew Payment | POST | /booking-api/payments/renew?session_id={id} | EMPTY | PaymentResponseDto | CUSTOMER |
| Get my Payments | GET | /booking-api/payments/my | EMPTY | List<PaymentResponseDto> | CUSTOMER |
| Get Payments for certain User | GET | /booking-api/payments?user_id={id} | EMPTY | List<PaymentResponseDto> | MANAGER |
Authentication is handled via JWT Bearer tokens.
- Register at
POST /auth/registeror login atPOST /auth/login - Copy the
tokenvalue from the response - Add it to all subsequent requests as a header:
Authorization: Bearer <your_token_here>
Role hierarchy:
| Role | Can do |
|---|---|
| PUBLIC | Register, Login |
| CUSTOMER | Browse accommodations & amenities, manage own bookings & payments |
| MANAGER | Everything CUSTOMER can do + manage accommodations, amenities, view all bookings/payments |
Passwords are stored using BCrypt encoding. Custom email validation is enforced on registration.
Managed by Liquibase with YAML changelogs. Tables created in order:
accommodations— type, location, size, daily_rate, availability, is_deletedamenities— name, description, is_deletedaccommodation_amenity— join table (many-to-many)users— email, password, first_name, last_name, is_deletedroles— role_name enumuser_role— join tablebookings— check_in_date, check_out_date, status, accommodation_id, user_id, is_deletedpayments— status, booking_id, user_id, session_url, session_id, amount, is_deleted
All entities use soft delete (is_deleted flag) rather than physical row removal.
This is a LogiPhysical model representing the booking database with entities and corresponding cardinalities.
The app integrates a Telegram Bot that sends messages to a configured admin chat when:
- A new booking is created
- A booking is canceled or expires
Configure the bot in your .env file using the TELEGRAM_BOT_TOKEN, TELEGRAM_BOT_USERNAME, and ADMIN_CHAT_ID variables. The chat ID can be obtained by messaging your bot and querying the Telegram Bot API's getUpdates endpoint.
The project has three layers of automated tests covering controllers, services, and repositories. All tests run against a real PostgreSQL instance managed by Testcontainers, ensuring they reflect actual database behaviour rather than in-memory approximations.
To run all tests:
mvn testAll controller tests use @SpringBootTest(webEnvironment = RANDOM_PORT) with a full Spring context, MockMvc wired through springSecurity(), and SQL scripts to seed/clean the database before and after each test. Authentication is handled via @WithMockUser for role-based endpoints, and via SecurityContextHolder injection for endpoints that read the currently authenticated user (e.g. GET /bookings/my).
Service tests use @ExtendWith(MockitoExtension.class) with @Mock for all dependencies and @InjectMocks for the implementation. No Spring context is loaded, making them fast and isolated. All collaborators are verified with verifyNoMoreInteractions() after each test.
Repository tests use @DataJpaTest with @AutoConfigureTestDatabase(replace = NONE), running against the real Testcontainers PostgreSQL instance. SQL scripts seed data at class or method level via @Sql annotations.
There are 83 tests that cover over 81% code lines of the whole application!

To be able to use a booking API in a convenient way, the swagger-ui was added. If the user forwards to localhost:${SPRING_LOCAL_PORT}/booking-api/swagger-ui/index.html/ then the useful interface with full documentation appears. Here are some screenshot examples:
User was not allowed to create a new amenity as it was not granted MANAGER authority.








