A plain-Java, in-memory implementation of a Library Management System, built as a Low-Level Design exercise. It models real library operations — cataloging books, registering members, issuing and returning books — using clear separation of responsibilities, interface-driven design, and SOLID principles. No external frameworks are required to build or run it.
Design and implement a Library Management System that supports:
- Adding and managing books in the library
- Registering and managing members
- Issuing a book to a member
- Returning a book
- Tracking whether a book is available or issued
- Preventing issuing of unavailable (already-issued) books
The system is split into four layers, each with one clear job:
┌─────────────────────────────────────────────────────────┐
│ Main (demo / entry point) │
├─────────────────────────────────────────────────────────┤
│ Service Layer -> LibraryService / LibraryServiceImpl│
│ (business rules: issue/return validation) │
├─────────────────────────────────────────────────────────┤
│ Repository Layer -> BookRepository / MemberRepository │
│ (storage abstraction — currently in-memory) │
├─────────────────────────────────────────────────────────┤
│ Model Layer -> Book, Member, IssueRecord, Status │
│ (plain data + minimal self-contained behavior) │
└─────────────────────────────────────────────────────────┘
Why this shape?
- Models stay dumb.
BookandMemberonly know about their own state (e.g. a Book can mark itself issued/available). They don't know how they're stored or what business rules govern issuing — that logic doesn't belong to them. - Repositories own storage.
BookRepository/MemberRepositoryare interfaces;InMemoryBookRepository/InMemoryMemberRepositoryare the current implementations. Swapping in a database-backed repository later requires zero changes to the service or model layers. - The service owns the rules. All business logic — "you can't issue
an already-issued book," "you can't return a book that isn't checked
out" — lives in
LibraryServiceImpl, which depends only on the repository interfaces (constructor injection), never the concrete in-memory classes.
| Principle | Where it shows up |
|---|---|
| Single Responsibility | Book/Member hold state only; LibraryServiceImpl holds business rules; repositories handle storage only. |
| Open/Closed | New storage backends can be added by implementing BookRepository/MemberRepository without modifying LibraryServiceImpl. |
| Liskov Substitution | Any BookRepository/MemberRepository implementation can replace InMemoryBookRepository/InMemoryMemberRepository without breaking the service. |
| Interface Segregation | Book storage and member storage are separate interfaces — a consumer needing only member data isn't forced to depend on book methods. |
| Dependency Inversion | LibraryServiceImpl depends on BookRepository/MemberRepository abstractions, injected via its constructor, not concrete classes. |
Model (com.library.model)
Book— id, ISBN, title, author, and currentBookStatus; can mark itself issued/available.BookStatus— enum:AVAILABLE,ISSUED.Member— id, name, email, and the set of book ids currently checked out to them.IssueRecord— one lending transaction (book, member, issue date, return date); preserves full lending history, not just current state.
Repository (com.library.repository)
BookRepository/InMemoryBookRepository— CRUD-style storage for books.MemberRepository/InMemoryMemberRepository— CRUD-style storage for members.
Service (com.library.service)
LibraryService— public contract: add book, register member, issue, return, list/search books.LibraryServiceImpl— enforces the rules: a book must exist and be available to be issued; a book must be currently issued to that specific member to be returned.
Exception (com.library.exception)
LibraryException— common base for all business exceptions.BookNotFoundException,MemberNotFoundException— referenced entity doesn't exist.BookNotAvailableException— attempted to issue a book that's already checked out.BookNotIssuedException— attempted to return a book that isn't checked out (or not to that member).DuplicateBookException,DuplicateMemberException— attempted to register a duplicate id.
Entry point
Main— wires the in-memory repositories and service together and runs a walkthrough demo (add books, register members, issue, a blocked double-issue, return, re-issue).LibraryServiceTest(insrc/test) — a small, dependency-free sanity-test runner covering the core rules.
library-management-system/
├── README.md
├── run.sh
└── src/
├── main/java/com/library/
│ ├── Main.java
│ ├── model/
│ │ ├── Book.java
│ │ ├── BookStatus.java
│ │ ├── Member.java
│ │ └── IssueRecord.java
│ ├── repository/
│ │ ├── BookRepository.java
│ │ ├── InMemoryBookRepository.java
│ │ ├── MemberRepository.java
│ │ └── InMemoryMemberRepository.java
│ ├── service/
│ │ ├── LibraryService.java
│ │ └── LibraryServiceImpl.java
│ └── exception/
│ ├── LibraryException.java
│ ├── BookNotFoundException.java
│ ├── MemberNotFoundException.java
│ ├── BookNotAvailableException.java
│ ├── BookNotIssuedException.java
│ ├── DuplicateBookException.java
│ └── DuplicateMemberException.java
└── test/java/com/library/
└── LibraryServiceTest.java
Requires JDK 11+ (no external dependencies, build tools, or libraries needed).
Option A — using the provided script:
./run.sh # builds everything and runs the demo (Main)
./run.sh test # builds everything and runs the sanity test suiteOption B — manually with javac/java:
mkdir -p out
find src/main/java src/test/java -name "*.java" > sources.txt
javac -d out @sources.txt
java -cp out com.library.Main # run the demo
java -cp out com.library.LibraryServiceTest # run the sanity tests- Add due dates / late fees by extending
IssueRecordand the service. - Add a
Reservationflow for books that are currently issued. - Swap
InMemoryBookRepository/InMemoryMemberRepositoryfor a JDBC- or file-backed implementation — no changes needed elsewhere, by design.