Capstone Project — Mini Library System
A multi-file challenge that pulls together classes, encapsulation, collections, polymorphism, and exception handling into one small program
You have now seen everything you need to build a small but honestly designed Java program. This capstone is a tiny library checkout system that exercises:
- Classes and constructors
- Encapsulation and immutability
- Collections (
List,Map) - Polymorphism through an interface
- Exception handling with a custom exception
- Layered organization (domain + service)
Take your time. There is intentionally more than one valid
implementation. Use the provided Main.java to drive your design.
The system, in plain English
A Library has a catalog of Books. A Member may borrow a copy
of a book — but only if a copy is available. When a member borrows
a book, the available-copy count for that book goes down by one;
when they return it, the count goes back up.
Borrowing an unknown book, or a book with no available copies,
throws a BorrowException. Returning a book that the member did
not borrow throws a BorrowException.
The expected behavior
Main.java is fixed. With a correct implementation it must print
exactly:
Catalog:
- Effective Java (2 copies)
- Refactoring (1 copies)
borrow ok? true
borrow ok? true
borrow failed: no copies available: Effective Java
borrow failed: unknown book: Unknown Title
After borrows:
- Effective Java (0 copies)
- Refactoring (1 copies)
ada has 1 loan(s)
grace has 1 loan(s)
return ok? true
After ada returns Effective Java:
- Effective Java (1 copies)
- Refactoring (1 copies)
return failed: not on loan to this member: RefactoringThe challenge
Implement four classes and one custom exception so that the provided Main produces the exact output shown above.
Book.java
- A class with private final fields
String titleandint totalCopies. - A private mutable field
int availableCopies. - Constructor
Book(String title, int totalCopies)setsavailableCopiesequal tototalCopies. - Methods
title(),totalCopies(),availableCopies(). - A package-private method
borrowOne()that decreasesavailableCopiesby 1. ThrowsBorrowExceptionwith message"no copies available: " + titleif none available. - A package-private method
returnOne()that increasesavailableCopiesby 1, but never beyondtotalCopies.
Member.java
- Private final
String name. PrivateList<Book> loansinitialised to a new ArrayList. - Constructor
Member(String name). - Methods
name()andloanCount()(returnsloans.size()). - Package-private
addLoan(Book b)andboolean removeLoan(Book b)(returns true if the book was on loan and is now removed, false otherwise).
BorrowException.java
- Extends
RuntimeException. Single-string-message constructor.
Library.java
- Private final
Map<String, Book> catalog(key = title). - Constructor
Library()initialises the map. - Method
void addBook(Book b)puts it in the catalog. - Method
List<Book> catalog()returns the books as a sorted-by-titleList(use aTreeMapor sort yourself). - Method
void borrow(Member m, String title)looks up the book, callsborrowOne(), and callsm.addLoan(book). ThrowsBorrowExceptionwith message"unknown book: " + titleif the title isn't in the catalog. - Method
void returnBook(Member m, String title)looks up the book, callsm.removeLoan(book)— if it returns false, throwBorrowException("not on loan to this member: " + title). Otherwise callbook.returnOne().
The given Main.java drives the behavior. Don't change it.
How the design uses the course's ideas
A short tour of the design choices in the reference solution:
- Encapsulation:
availableCopiesis private. OnlyBook.borrowOneandBook.returnOnemay change it, and only inside the package. This is exactly the "single place that owns the rule" pattern. - Visibility:
borrowOneandreturnOneare package-private, notpublic. They are intended forLibraryto use, not random outside code — outside code goes throughLibrary.borrow. - Custom exception:
BorrowExceptionis a domain-specific unchecked exception. The service throws it; the UI layer (Main) catches it and turns it into user-facing output. - Map for lookup: the catalog is a
Map<String, Book>because we look books up by title — exactly the case for a Map. - Polymorphism / interfaces: not used heavily here, on purpose
— they shine when there's a choice of implementations. Adding
a
BookRepositoryinterface with both in-memory and persistent versions would be the natural next step.
Once your solution passes the test, take a few minutes to:
- Add a new method
int totalAvailable()onLibrary. - Try restricting members to a maximum of 3 simultaneous loans.
- Try adding a
LinkedHashMapinstead ofTreeMapand observe what changes in the output. (You will need to also change iteration order expectations.)
These small extensions are how real codebases evolve.
You're done with the capstone. The final page is a roadmap for where to go next.