Goal: create the first three LMS classes with hidden data and validation, and run them from a Main.
Tip Open this page next to your IDE. Type the code yourself. Copy-paste teaches your fingers, not your brain. The full reference files are collapsed under each heading; open them to compare after you tried.
What you will have at the end: a console program that registers books and members, lists them, and rejects bad input.

1. Create the project
Make a folder lms-<index> (your cloned repository) and this structure. Package names must match folder names.
lms-<index>/
├── .gitignore
├── README.md
├── docs/
└── src/
└── lms/
├── Main.java
├── model/
│ ├── Book.java
│ └── Member.java
└── service/
└── Library.java
Start your Git branch first: git checkout -b week2-encapsulation.
2. Write Book
Four private fields, a constructor that calls the setters, getters, and setters that reject blank text. Notice the constructor never assigns this.title = title directly. That would let a blank title slip in.
Tip Commit as soon as Book compiles: "Add Book with validated setters".
src/lms/model/Book.javaFull reference file
package lms.model; public class Book { private String title; private String author; private String isbn; private boolean isBorrowed; public Book(String title, String author, String isbn) { setTitle(title); setAuthor(author); setIsbn(isbn); this.isBorrowed = false; } public String getTitle() { return title; } public String getAuthor() { return author; } public String getIsbn() { return isbn; } public boolean isBorrowed() { return isBorrowed; } public void setTitle(String title) { if (title == null || title.isBlank()) throw new IllegalArgumentException("Title cannot be blank"); this.title = title; } public void setAuthor(String author) { if (author == null || author.isBlank()) throw new IllegalArgumentException("Author cannot be blank"); this.author = author; } public void setIsbn(String isbn) { if (isbn == null || isbn.isBlank()) throw new IllegalArgumentException("ISBN cannot be blank"); this.isbn = isbn; } public void setBorrowed(boolean borrowed) { this.isBorrowed = borrowed; } @Override public String toString() { return "Book{title='" + title + "', author='" + author + "', isbn='" + isbn + "', borrowed=" + isBorrowed + "}"; } }
3. Write Member
Same pattern: private fields, validated setters, a constructor that uses them. setBorrowedCount refuses negative numbers, and incrementBorrowedCount() goes through it.
src/lms/model/Member.javaFull reference file
package lms.model; public class Member { private String memberId; private String name; private int borrowedCount; public Member(String memberId, String name) { setMemberId(memberId); setName(name); this.borrowedCount = 0; } public String getMemberId() { return memberId; } public String getName() { return name; } public int getBorrowedCount() { return borrowedCount; } public void setMemberId(String memberId) { if (memberId == null || memberId.isBlank()) throw new IllegalArgumentException("Member ID cannot be blank"); this.memberId = memberId; } public void setName(String name) { if (name == null || name.isBlank()) throw new IllegalArgumentException("Name cannot be blank"); this.name = name; } public void setBorrowedCount(int borrowedCount) { if (borrowedCount < 0) throw new IllegalArgumentException("Borrowed count cannot be negative"); this.borrowedCount = borrowedCount; } public void incrementBorrowedCount() { setBorrowedCount(this.borrowedCount + 1); } @Override public String toString() { return "Member{id='" + memberId + "', name='" + name + "', borrowedCount=" + borrowedCount + "}"; } }
4. Write Library
Library has books and members, which is composition. It keeps two private lists and offers methods instead of exposing them.
src/lms/service/Library.javaFull reference file
package lms.service; import lms.model.Book; import lms.model.Member; import java.util.ArrayList; import java.util.List; public class Library { private final List<Book> books = new ArrayList<>(); private final List<Member> members = new ArrayList<>(); public void addBook(Book book) { books.add(book); } public void addMember(Member member) { members.add(member); } public void listBooks() { System.out.println("--- Books in the library ---"); for (Book b : books) System.out.println(b); } public void listMembers() { System.out.println("--- Registered members ---"); for (Member m : members) System.out.println(m); } public List<Book> getBooks() { return books; } public List<Member> getMembers() { return members; } }
5. Test from Main
Always include one bad input, to prove your validation works.
src/lms/Main.javaTest driver
package lms; import lms.model.Book; import lms.model.Member; import lms.service.Library; public class Main { public static void main(String[] args) { Library library = new Library(); library.addBook(new Book("Clean Code", "Robert C. Martin", "978-0132350884")); library.addBook(new Book("Effective Java", "Joshua Bloch", "978-0134685991")); library.addBook(new Book("The Pragmatic Programmer", "Hunt & Thomas", "978-0135957059")); library.addMember(new Member("M001", "Nadeesha Perera")); library.addMember(new Member("M002", "Kasun Silva")); library.listBooks(); library.listMembers(); try { library.addBook(new Book("", "Nobody", "000-0000000000")); } catch (IllegalArgumentException e) { System.out.println("Rejected invalid book: " + e.getMessage()); } } }
If it does not work
| Symptom | Fix |
|---|---|
package lms.model does not exist |
Folder names must equal package names; compile from the project root |
Prints null as title |
Missing this. in the setter |
No Rejected line |
The bad book was accepted: the constructor assigns directly instead of calling setTitle |
Check Make title public and write book.title = ""; in Main. It compiles and silently breaks the object. That is why we hide data.
Checkpoint: is it done?
- All fields in all three classes are
private - Each constructor calls its setters
- The blank-title book is rejected with
Title cannot be blank - UML saved as
docs/uml-week2.png; PR opened: Week 2: Classes, Objects & Encapsulation