Two passengers, one seat, no double booking
A bus booking system with a live seat map. When two people grab the same seat at the same moment, only one of them gets it.
Team semester project at CTU FEL by Mykhailo Plokhin and Ivan Shestachenko. A demo, not a real carrier.
Java · Spring Boot · PostgreSQL · React · Docker · k3s


The problem
Booking systems break exactly when two people want the same thing at the same time — which is also when the business earns money.
- If the app reads a seat and then writes it, two requests can both see it free and both book it.
- Overbooking is often repaired afterwards with a nightly job and an apology, instead of being impossible in the first place.
- Passengers and dispatchers need different rights, not the same endpoint behind a different screen.
How it is built
One service owns reservations and nothing else writes a seat. A contested seat goes through one short transaction that holds a row lock.

Passenger A books seat 12. The database locks that row.
Passenger B asks for seat 12 at the same moment and waits for the lock.
A's booking commits. B is told the seat is taken, not double-booked.
Key decisions
Each choice, and the alternative it beat.
Lock the contested seat row
The second request waits instead of overwriting, and the wait is short because the hold is the only work in the transaction. Optimistic retries were the alternative; on a full route they turn into a storm of retries.
One write path for reservations
The timetable, the seat map and the dispatcher only read. With a single writer, "one seat, one passenger" is a rule the system can actually check.
Roles checked at the endpoint
Spring Security resolves the role before the handler runs, so a dispatcher action cannot be reached from a passenger URL.
Numbers
Omnibus is a university project with a live demo, not a commercial service. Measured figures are checked in the code; a target is what it is designed for.
| Figure | What it means | Basis |
|---|---|---|
| ZeroDouble bookings | The design target: the database refuses the second writer instead of a cleanup job fixing it later.Target | Target |
| FOR UPDATESeat lock | A pessimistic row lock on the seat query (BookedSegmentRepository).Measured | Measured |
| k3sRuns on | Containers on a k3s cluster — the address linked from this page.Measured | Measured |
- Target
- — What the build is designed for — not a measured result
- Measured
- — Checked in the code or on the live build
Need something similar?
Tell us what you need in a few lines. A person from the team replies.
Start a project

