# library-management Companion code for the ankurm.com article **Library Management System in Java: Console Version to Spring Boot REST API**. One module, three stages: the same lending rules, first in plain Java, then split into classes, then served over HTTP with Spring Boot 4 and JPA. Every number, status code and SQL line quoted in the article comes from a file in [`output/`](output/). The tests write those files while asserting the same numbers, so the transcripts cannot drift from the code. ## Versions | Component | Version | Verified against | |---|---|---| | JDK | **25.0.5** (Temurin, LTS) | `java -version` | | Spring Boot | **4.1.2** (4.1.0 went GA on 2026-06-10) | `maven-metadata.xml` on Maven Central | | Spring Framework | 7.0.10 | `mvn dependency:list` | | Hibernate ORM | 7.4.13.Final | `mvn dependency:list` | | Spring Data JPA | 4.1.2 | `mvn dependency:list` | | Tomcat (embedded) | 11.0.27 | `mvn dependency:list` | | H2 | 2.4.240 | `mvn dependency:list` | | Maven | 3.9.11 | `mvn -v` | ## The three stages | Stage | Where | Run it | |---|---|---| | 1. One file, no build tool | `LibraryConsole.java` (attached to the article, not stored here) | `java LibraryConsole.java < scripts/demo-input.txt` | | 2. Split into classes, rules extracted | `src/main/java/com/ankurm/library/console/` + `core/LoanPolicy.java` | `mvn -q compile && java -cp target/classes com.ankurm.library.console.ConsoleApp < scripts/demo-input.txt` | | 3. Spring Boot REST API with JPA | `src/main/java/com/ankurm/library/api/` + `core/LoanPolicy.java` | `scripts/run.sh` | `core/LoanPolicy.java` (14-day loans, 3 active loans, Rs 2 a day fine) is shared by stages 2 and 3 and did not change when the app moved to a database. ## Quickstart ```bash export JAVA_HOME=/path/to/jdk-25 && export PATH="$JAVA_HOME/bin:$PATH" mvn test # 6 tests; also regenerates output/02 to output/06 scripts/run.sh # API on http://localhost:8080 (in-memory H2, clean library per start) scripts/api-demo.sh # in another terminal: curl walk-through ``` The first Maven run must be online so the plugins are cached; `-o` works afterwards. `scripts/run-all.sh` regenerates every file in `output/`. ## Endpoints | Method and path | Purpose | Refusals | |---|---|---| | `POST /api/books` | add a book with N copies | 400 bad fields, 409 duplicate ISBN | | `GET /api/books` | list books with copies available | | | `POST /api/members` | register a member | 400 bad fields, 409 duplicate email | | `POST /api/loans` | borrow: `{"memberId":1,"bookId":2}` | 404 unknown id, 409 no copies, 422 limit or duplicate loan | | `POST /api/loans/{id}/return` | return, optional `{"returnedOn":"2026-11-20"}` | 409 already returned, 422 before borrow date | | `GET /api/loans` | all loans, one SQL statement | | Errors are RFC 9457 `application/problem+json`. ## Captured output | File | What it shows | Produced by | |---|---|---| | `01-console-structured.txt` | stage 2 run of `scripts/demo-input.txt` (identical to stage 1) | `run-all.sh` | | `02-loan-policy.txt` | the fine table | `LoanPolicyTest` | | `03-rest-api.txt` | every success and refusal over MockMvc | `LibraryApiTest` | | `04-query-count.txt` | 11 SQL statements versus 1 for the same list | `QueryCountTest` | | `05-race-with-version.txt` | 8 threads, last copy, `@Version`: one winner | `BorrowRaceTest` | | `06-race-without-version.txt` | same race without `@Version`: 8 loans for 1 copy | `BorrowRaceTest` | | `07-curl-demo.txt` | the same API on a real Tomcat, driven with curl (dates are the day it ran) | `scripts/api-demo.sh` | | `08-open-in-view-warning.txt` | the Boot default this module overrides | `scripts/open-in-view-warning.sh` | | `09-borrow-sql.txt` | the exact SQL one borrow sends, including `where id=? and version=?` | `scripts/sql-trace.sh` | Timing-dependent detail: in `05` the 7 refused borrows split between "optimistic-lock failure" (a thread read the book before the winner committed) and "any other reason" (a thread started late and found no copy left). Over repeated runs I saw splits ranging from 7/0 to 4/3, which is why the transcript counts them separately. The invariants (1 winner, 1 loan, 0 copies left) are asserted.