Inventory Management (REST)
Ten concurrent requests, three units of stock: the naive path oversold every single trial, the fixed one never did.
- Java
- Spring Boot
- MySQL
About this project
A REST API for warehouse or shop stock: create products, receive stock, reserve it for an order, release a reservation that fell through, and see every accepted movement — and every rejected attempt — in a full audit trail. Java 21, Spring Boot 4.1, Spring Data JPA, MySQL, built with the Maven wrapper so a clone-and-build needs nothing but a JDK.
Underneath, it is a project about one guarantee: when stock is nearly gone and two orders both try to claim the last units at the same instant, the database must never confirm more units than actually exist. Rather than assert that the code keeps it, this project puts ten concurrent HTTP requests against three units of real stock and counts what actually happens, with a naive implementation and a fixed one, side by side, against the same running server — the naive path (a raw SQL read-then-write that steps around the entity layer's own @Version column) oversold in 100% of measured trials, confirming all ten requests every time; the fixed path (real JPA optimistic locking with a bounded retry loop) confirmed exactly three and rejected exactly seven, every single trial. Built as a template on purpose: the retry pattern does not know or care that it is managing inventory, and the README's first suggested extension is forking Product onto an entity that has nothing to do with a warehouse, to prove the pattern generalises rather than assume it does.
What you will learn
- Reproduce a real stock-overselling race condition with concurrent HTTP requests, measured, not simulated
- Fix it with JPA optimistic locking (@Version) and a bounded retry loop, and explain why over a raw lock
- Understand the Spring self-invocation/proxy trap that silently defeats @Transactional if you get it wrong
- Build a global exception handler that returns a real status code for every expected failure, never a raw 500
- Fork a concurrency-safe pattern onto a different domain entity, and prove the fork still holds under load
Before you start
- Java and some Spring Boot — controllers, services, Spring Data JPA basics
- A JDK 21+ — no global Maven install needed, the bundled wrapper downloads its own
- MySQL 8+ reachable somewhere — the README covers pointing it at your own instance
New to Java? The free Java course covers everything this project assumes.
Academic integrity notice
These projects are provided for learning, experimentation and reference. Understand the code, modify it for your own requirements, and acknowledge external work according to your institution’s academic policies. Priodemy does not supply work to be submitted as your own.
Features
- Full REST CRUD plus receive/reserve/release stock movements, with a complete audit trail
- A real naive-vs-fixed race condition demo (RaceHarness.java) against a genuinely running server
- JPA optimistic locking (@Version) with a bounded retry loop — and a documented reason for choosing it over a pessimistic lock
- A global exception handler mapping every expected failure to a real status code — no raw 500s
- Ships the Maven wrapper — builds with just a JDK, no global Maven install, downloads confirmed working
- Built explicitly as a forkable template: swap Product for any entity with a quantity to protect
Technology stack
Track: Java · Level: Major project
What is included
- A full Maven project — pom.xml, mvnw/mvnw.cmd, 22 source files across domain/repository/dto/exception/service/web
- RaceHarness.java — a standalone, dependency-free concurrent-request proof, run separately
- A README on the race condition, the fix, the self-invocation trap, and two real bugs found by testing it
Delivered as a 59 KB ZIP. SHA-256 published under Changelog so you can verify it.
System requirements
- Operating system
- Any
- Runtime
- JDK 21+. MySQL 8+. No global Maven needed — the bundled wrapper downloads its own.
- Database
- MySQL
Licence
Learn from it, change it, and use it in your own coursework and personal projects. Do not resell it or pass it off as your own original work.
- Personal and educational use
- Modifying the code however you like
- Using it as the basis of your own coursework, with honest acknowledgement
- Putting your modified version in a portfolio or on your own GitHub
- Reselling or redistributing the project, modified or not
- Presenting it as your own original work
- Commercial deployment — see the Commercial licence
Support window: 6 months from download.
Frequently asked
Is the race condition really reproduced, or just described?
Reproduced. RaceHarness.java fires ten real concurrent HTTP requests at a genuinely running instance of this project and counts what the database actually confirmed, every time you run it — including a version with the naive path's artificial delay set to zero, which still oversold every trial.
Do I need to know Spring Boot already?
Some familiarity helps, but the README explains every mechanism it uses from the ground up — including a subtle Spring proxy trap (calling a @Transactional method from inside its own class silently skips the transaction) that the project's own design works around, and explains why.
Is this really reusable for something other than inventory?
That is the explicit design goal, not a side claim. The optimistic-locking and retry code in StockService does not reference inventory at all — the README's first suggested extension is forking Product onto an unrelated entity (a Seat, a Ticket) and confirming the same code holds with zero changes.
Changelog
- First release
SHA-25667550d18768d44d1330d63a53735c07b19d035102f6ef8f365283dc12aecbaab