Library Management System in Java

Overview

A comprehensive Library Management System that handles books, members, borrowing, returns, and administrative functions with proper data persistence and user interfaces.

System Architecture

1. Core Domain Models

// Base Entity class
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Objects;
public abstract class BaseEntity<ID> implements Serializable {
private ID id;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public BaseEntity() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
// Getters and setters
public ID getId() { return id; }
public void setId(ID id) { this.id = id; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BaseEntity<?> that = (BaseEntity<?>) o;
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
// Book entity
import java.time.Year;
import java.util.ArrayList;
import java.util.List;
public class Book extends BaseEntity<String> {
private String isbn;
private String title;
private String author;
private String publisher;
private Year publicationYear;
private String genre;
private int totalCopies;
private int availableCopies;
private List<BookCopy> copies;
public Book() {
this.copies = new ArrayList<>();
}
public Book(String isbn, String title, String author, String publisher, 
Year publicationYear, String genre, int totalCopies) {
this();
this.isbn = isbn;
this.title = title;
this.author = author;
this.publisher = publisher;
this.publicationYear = publicationYear;
this.genre = genre;
this.totalCopies = totalCopies;
this.availableCopies = totalCopies;
generateCopies();
}
private void generateCopies() {
for (int i = 1; i <= totalCopies; i++) {
copies.add(new BookCopy(this, i));
}
}
// Getters and setters
public String getIsbn() { return isbn; }
public void setIsbn(String isbn) { this.isbn = isbn; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public String getPublisher() { return publisher; }
public void setPublisher(String publisher) { this.publisher = publisher; }
public Year getPublicationYear() { return publicationYear; }
public void setPublicationYear(Year publicationYear) { this.publicationYear = publicationYear; }
public String getGenre() { return genre; }
public void setGenre(String genre) { this.genre = genre; }
public int getTotalCopies() { return totalCopies; }
public void setTotalCopies(int totalCopies) { 
this.totalCopies = totalCopies;
updateAvailableCopies();
}
public int getAvailableCopies() { return availableCopies; }
public void setAvailableCopies(int availableCopies) { this.availableCopies = availableCopies; }
public List<BookCopy> getCopies() { return copies; }
public void setCopies(List<BookCopy> copies) { this.copies = copies; }
public void updateAvailableCopies() {
this.availableCopies = (int) copies.stream()
.filter(BookCopy::isAvailable)
.count();
}
public boolean isAvailable() {
return availableCopies > 0;
}
@Override
public String toString() {
return String.format("Book{isbn='%s', title='%s', author='%s', available=%d/%d}", 
isbn, title, author, availableCopies, totalCopies);
}
}
// Book Copy entity
public class BookCopy extends BaseEntity<Long> {
private Book book;
private int copyNumber;
private boolean available;
private String condition; // NEW, GOOD, FAIR, POOR
private String location; // Shelf location
public BookCopy() {
this.available = true;
this.condition = "NEW";
}
public BookCopy(Book book, int copyNumber) {
this();
this.book = book;
this.copyNumber = copyNumber;
}
// Getters and setters
public Book getBook() { return book; }
public void setBook(Book book) { this.book = book; }
public int getCopyNumber() { return copyNumber; }
public void setCopyNumber(int copyNumber) { this.copyNumber = copyNumber; }
public boolean isAvailable() { return available; }
public void setAvailable(boolean available) { this.available = available; }
public String getCondition() { return condition; }
public void setCondition(String condition) { this.condition = condition; }
public String getLocation() { return location; }
public void setLocation(String location) { this.location = location; }
public String getUniqueId() {
return book.getIsbn() + "-" + copyNumber;
}
@Override
public String toString() {
return String.format("BookCopy{book='%s', copyNumber=%d, available=%s}", 
book.getTitle(), copyNumber, available);
}
}
// Member entity
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
public class Member extends BaseEntity<Long> {
private String memberId;
private String firstName;
private String lastName;
private String email;
private String phone;
private LocalDate joinDate;
private MembershipType membershipType;
private int maxBooksAllowed;
private double outstandingFines;
private List<BorrowRecord> borrowHistory;
public enum MembershipType {
STUDENT(5, 14),
FACULTY(10, 30),
STAFF(5, 21),
PREMIUM(15, 45);
private final int maxBooks;
private final int loanPeriodDays;
MembershipType(int maxBooks, int loanPeriodDays) {
this.maxBooks = maxBooks;
this.loanPeriodDays = loanPeriodDays;
}
public int getMaxBooks() { return maxBooks; }
public int getLoanPeriodDays() { return loanPeriodDays; }
}
public Member() {
this.borrowHistory = new ArrayList<>();
this.joinDate = LocalDate.now();
this.outstandingFines = 0.0;
}
public Member(String memberId, String firstName, String lastName, 
String email, String phone, MembershipType membershipType) {
this();
this.memberId = memberId;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.phone = phone;
this.membershipType = membershipType;
this.maxBooksAllowed = membershipType.getMaxBooks();
}
// Getters and setters
public String getMemberId() { return memberId; }
public void setMemberId(String memberId) { this.memberId = memberId; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getFullName() { return firstName + " " + lastName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public LocalDate getJoinDate() { return joinDate; }
public void setJoinDate(LocalDate joinDate) { this.joinDate = joinDate; }
public MembershipType getMembershipType() { return membershipType; }
public void setMembershipType(MembershipType membershipType) { 
this.membershipType = membershipType;
this.maxBooksAllowed = membershipType.getMaxBooks();
}
public int getMaxBooksAllowed() { return maxBooksAllowed; }
public void setMaxBooksAllowed(int maxBooksAllowed) { this.maxBooksAllowed = maxBooksAllowed; }
public double getOutstandingFines() { return outstandingFines; }
public void setOutstandingFines(double outstandingFines) { this.outstandingFines = outstandingFines; }
public List<BorrowRecord> getBorrowHistory() { return borrowHistory; }
public void setBorrowHistory(List<BorrowRecord> borrowHistory) { this.borrowHistory = borrowHistory; }
public int getCurrentlyBorrowedCount() {
return (int) borrowHistory.stream()
.filter(record -> record.getReturnDate() == null)
.count();
}
public boolean canBorrowMoreBooks() {
return getCurrentlyBorrowedCount() < maxBooksAllowed && outstandingFines == 0;
}
public void addFine(double amount) {
this.outstandingFines += amount;
}
public void payFine(double amount) {
this.outstandingFines = Math.max(0, this.outstandingFines - amount);
}
@Override
public String toString() {
return String.format("Member{id='%s', name='%s %s', type=%s, fines=$%.2f}", 
memberId, firstName, lastName, membershipType, outstandingFines);
}
}
// Borrow Record entity
import java.time.LocalDate;
public class BorrowRecord extends BaseEntity<Long> {
private Member member;
private BookCopy bookCopy;
private LocalDate borrowDate;
private LocalDate dueDate;
private LocalDate returnDate;
private double fineAmount;
private String status; // ACTIVE, RETURNED, OVERDUE
public BorrowRecord() {
this.borrowDate = LocalDate.now();
this.status = "ACTIVE";
}
public BorrowRecord(Member member, BookCopy bookCopy) {
this();
this.member = member;
this.bookCopy = bookCopy;
this.dueDate = borrowDate.plusDays(member.getMembershipType().getLoanPeriodDays());
}
// Getters and setters
public Member getMember() { return member; }
public void setMember(Member member) { this.member = member; }
public BookCopy getBookCopy() { return bookCopy; }
public void setBookCopy(BookCopy bookCopy) { this.bookCopy = bookCopy; }
public LocalDate getBorrowDate() { return borrowDate; }
public void setBorrowDate(LocalDate borrowDate) { this.borrowDate = borrowDate; }
public LocalDate getDueDate() { return dueDate; }
public void setDueDate(LocalDate dueDate) { this.dueDate = dueDate; }
public LocalDate getReturnDate() { return returnDate; }
public void setReturnDate(LocalDate returnDate) { this.returnDate = returnDate; }
public double getFineAmount() { return fineAmount; }
public void setFineAmount(double fineAmount) { this.fineAmount = fineAmount; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public boolean isOverdue() {
return LocalDate.now().isAfter(dueDate) && returnDate == null;
}
public long getDaysOverdue() {
if (!isOverdue()) return 0;
return LocalDate.now().toEpochDay() - dueDate.toEpochDay();
}
public void calculateFine(double dailyFineRate) {
if (isOverdue()) {
long overdueDays = getDaysOverdue();
this.fineAmount = overdueDays * dailyFineRate;
}
}
@Override
public String toString() {
return String.format("BorrowRecord{member=%s, book=%s, due=%s, returned=%s}", 
member.getFullName(), bookCopy.getBook().getTitle(), 
dueDate, returnDate != null ? returnDate.toString() : "Not returned");
}
}

2. Repository Layer

// Generic Repository Interface
import java.util.List;
import java.util.Optional;
public interface Repository<T extends BaseEntity<ID>, ID> {
Optional<T> findById(ID id);
List<T> findAll();
T save(T entity);
void delete(T entity);
void deleteById(ID id);
boolean existsById(ID id);
long count();
}
// Book Repository
import java.util.*;
public class BookRepository implements Repository<Book, String> {
private final Map<String, Book> books = new HashMap<>();
private final Map<String, List<BookCopy>> copiesByIsbn = new HashMap<>();
@Override
public Optional<Book> findById(String isbn) {
return Optional.ofNullable(books.get(isbn));
}
@Override
public List<Book> findAll() {
return new ArrayList<>(books.values());
}
@Override
public Book save(Book book) {
books.put(book.getIsbn(), book);
copiesByIsbn.put(book.getIsbn(), book.getCopies());
return book;
}
@Override
public void delete(Book book) {
books.remove(book.getIsbn());
copiesByIsbn.remove(book.getIsbn());
}
@Override
public void deleteById(String isbn) {
books.remove(isbn);
copiesByIsbn.remove(isbn);
}
@Override
public boolean existsById(String isbn) {
return books.containsKey(isbn);
}
@Override
public long count() {
return books.size();
}
// Custom queries
public List<Book> findByTitle(String title) {
return books.values().stream()
.filter(book -> book.getTitle().toLowerCase().contains(title.toLowerCase()))
.toList();
}
public List<Book> findByAuthor(String author) {
return books.values().stream()
.filter(book -> book.getAuthor().toLowerCase().contains(author.toLowerCase()))
.toList();
}
public List<Book> findByGenre(String genre) {
return books.values().stream()
.filter(book -> book.getGenre().equalsIgnoreCase(genre))
.toList();
}
public List<Book> findAvailableBooks() {
return books.values().stream()
.filter(Book::isAvailable)
.toList();
}
public Optional<BookCopy> findAvailableCopy(String isbn) {
return copiesByIsbn.getOrDefault(isbn, Collections.emptyList()).stream()
.filter(BookCopy::isAvailable)
.findFirst();
}
}
// Member Repository
import java.util.*;
public class MemberRepository implements Repository<Member, Long> {
private final Map<Long, Member> members = new HashMap<>();
private final Map<String, Member> membersById = new HashMap<>();
private long nextId = 1;
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(members.get(id));
}
public Optional<Member> findByMemberId(String memberId) {
return Optional.ofNullable(membersById.get(memberId));
}
@Override
public List<Member> findAll() {
return new ArrayList<>(members.values());
}
@Override
public Member save(Member member) {
if (member.getId() == null) {
member.setId(nextId++);
}
members.put(member.getId(), member);
membersById.put(member.getMemberId(), member);
return member;
}
@Override
public void delete(Member member) {
members.remove(member.getId());
membersById.remove(member.getMemberId());
}
@Override
public void deleteById(Long id) {
Member member = members.get(id);
if (member != null) {
membersById.remove(member.getMemberId());
members.remove(id);
}
}
@Override
public boolean existsById(Long id) {
return members.containsKey(id);
}
public boolean existsByMemberId(String memberId) {
return membersById.containsKey(memberId);
}
@Override
public long count() {
return members.size();
}
// Custom queries
public List<Member> findByName(String name) {
return members.values().stream()
.filter(member -> member.getFullName().toLowerCase().contains(name.toLowerCase()))
.toList();
}
public List<Member> findMembersWithFines() {
return members.values().stream()
.filter(member -> member.getOutstandingFines() > 0)
.toList();
}
public List<Member> findByMembershipType(Member.MembershipType type) {
return members.values().stream()
.filter(member -> member.getMembershipType() == type)
.toList();
}
}
// Borrow Record Repository
import java.time.LocalDate;
import java.util.*;
public class BorrowRecordRepository implements Repository<BorrowRecord, Long> {
private final Map<Long, BorrowRecord> records = new HashMap<>();
private long nextId = 1;
@Override
public Optional<BorrowRecord> findById(Long id) {
return Optional.ofNullable(records.get(id));
}
@Override
public List<BorrowRecord> findAll() {
return new ArrayList<>(records.values());
}
@Override
public BorrowRecord save(BorrowRecord record) {
if (record.getId() == null) {
record.setId(nextId++);
}
records.put(record.getId(), record);
return record;
}
@Override
public void delete(BorrowRecord record) {
records.remove(record.getId());
}
@Override
public void deleteById(Long id) {
records.remove(id);
}
@Override
public boolean existsById(Long id) {
return records.containsKey(id);
}
@Override
public long count() {
return records.size();
}
// Custom queries
public List<BorrowRecord> findByMember(Long memberId) {
return records.values().stream()
.filter(record -> record.getMember().getId().equals(memberId))
.toList();
}
public List<BorrowRecord> findByBookCopy(Long bookCopyId) {
return records.values().stream()
.filter(record -> record.getBookCopy().getId().equals(bookCopyId))
.toList();
}
public List<BorrowRecord> findOverdueRecords() {
return records.values().stream()
.filter(BorrowRecord::isOverdue)
.toList();
}
public List<BorrowRecord> findActiveRecords() {
return records.values().stream()
.filter(record -> record.getReturnDate() == null)
.toList();
}
public Optional<BorrowRecord> findActiveRecordByBookCopy(Long bookCopyId) {
return records.values().stream()
.filter(record -> record.getBookCopy().getId().equals(bookCopyId))
.filter(record -> record.getReturnDate() == null)
.findFirst();
}
}

3. Service Layer

// Library Service Interface
import java.util.List;
import java.util.Optional;
public interface LibraryService {
// Book operations
Book addBook(Book book);
Optional<Book> findBookByIsbn(String isbn);
List<Book> searchBooksByTitle(String title);
List<Book> searchBooksByAuthor(String author);
List<Book> getAvailableBooks();
// Member operations
Member registerMember(Member member);
Optional<Member> findMemberById(String memberId);
List<Member> searchMembersByName(String name);
// Borrow operations
BorrowResult borrowBook(String memberId, String isbn);
ReturnResult returnBook(String memberId, String isbn, int copyNumber);
List<BorrowRecord> getMemberBorrowHistory(String memberId);
List<BorrowRecord> getOverdueRecords();
// Fine operations
double calculateMemberFines(String memberId);
PaymentResult payFine(String memberId, double amount);
// Reports
LibraryReport generateReport();
}
// Main Library Service Implementation
import java.time.LocalDate;
import java.util.*;
public class LibraryServiceImpl implements LibraryService {
private final BookRepository bookRepository;
private final MemberRepository memberRepository;
private final BorrowRecordRepository borrowRecordRepository;
private final double dailyFineRate = 0.50; // $0.50 per day
private final int maxRenewals = 2;
public LibraryServiceImpl(BookRepository bookRepository, 
MemberRepository memberRepository, 
BorrowRecordRepository borrowRecordRepository) {
this.bookRepository = bookRepository;
this.memberRepository = memberRepository;
this.borrowRecordRepository = borrowRecordRepository;
}
// Book operations
@Override
public Book addBook(Book book) {
if (bookRepository.existsById(book.getIsbn())) {
throw new LibraryException("Book with ISBN " + book.getIsbn() + " already exists");
}
return bookRepository.save(book);
}
@Override
public Optional<Book> findBookByIsbn(String isbn) {
return bookRepository.findById(isbn);
}
@Override
public List<Book> searchBooksByTitle(String title) {
return bookRepository.findByTitle(title);
}
@Override
public List<Book> searchBooksByAuthor(String author) {
return bookRepository.findByAuthor(author);
}
@Override
public List<Book> getAvailableBooks() {
return bookRepository.findAvailableBooks();
}
// Member operations
@Override
public Member registerMember(Member member) {
if (memberRepository.existsByMemberId(member.getMemberId())) {
throw new LibraryException("Member with ID " + member.getMemberId() + " already exists");
}
return memberRepository.save(member);
}
@Override
public Optional<Member> findMemberById(String memberId) {
return memberRepository.findByMemberId(memberId);
}
@Override
public List<Member> searchMembersByName(String name) {
return memberRepository.findByName(name);
}
// Borrow operations
@Override
public BorrowResult borrowBook(String memberId, String isbn) {
Member member = memberRepository.findByMemberId(memberId)
.orElseThrow(() -> new LibraryException("Member not found: " + memberId));
Book book = bookRepository.findById(isbn)
.orElseThrow(() -> new LibraryException("Book not found: " + isbn));
// Validation checks
if (!member.canBorrowMoreBooks()) {
return BorrowResult.failure("Member cannot borrow more books. Current: " + 
member.getCurrentlyBorrowedCount() + "/" + member.getMaxBooksAllowed() +
", Fines: $" + member.getOutstandingFines());
}
if (!book.isAvailable()) {
return BorrowResult.failure("No available copies of: " + book.getTitle());
}
BookCopy availableCopy = bookRepository.findAvailableCopy(isbn)
.orElseThrow(() -> new LibraryException("No available copy found"));
// Create borrow record
BorrowRecord record = new BorrowRecord(member, availableCopy);
availableCopy.setAvailable(false);
book.updateAvailableCopies();
borrowRecordRepository.save(record);
member.getBorrowHistory().add(record);
return BorrowResult.success(record, "Book borrowed successfully. Due date: " + record.getDueDate());
}
@Override
public ReturnResult returnBook(String memberId, String isbn, int copyNumber) {
Member member = memberRepository.findByMemberId(memberId)
.orElseThrow(() -> new LibraryException("Member not found: " + memberId));
Book book = bookRepository.findById(isbn)
.orElseThrow(() -> new LibraryException("Book not found: " + isbn));
// Find the active borrow record
BorrowRecord activeRecord = borrowRecordRepository.findActiveRecords().stream()
.filter(record -> record.getMember().getMemberId().equals(memberId))
.filter(record -> record.getBookCopy().getBook().getIsbn().equals(isbn))
.filter(record -> record.getBookCopy().getCopyNumber() == copyNumber)
.findFirst()
.orElseThrow(() -> new LibraryException("No active borrow record found"));
// Update record
activeRecord.setReturnDate(LocalDate.now());
activeRecord.setStatus("RETURNED");
// Calculate fine if overdue
if (activeRecord.isOverdue()) {
activeRecord.calculateFine(dailyFineRate);
member.addFine(activeRecord.getFineAmount());
}
// Make book copy available again
activeRecord.getBookCopy().setAvailable(true);
book.updateAvailableCopies();
return ReturnResult.success(activeRecord, 
"Book returned successfully." + 
(activeRecord.getFineAmount() > 0 ? 
" Overdue fine: $" + activeRecord.getFineAmount() : ""));
}
@Override
public List<BorrowRecord> getMemberBorrowHistory(String memberId) {
Member member = memberRepository.findByMemberId(memberId)
.orElseThrow(() -> new LibraryException("Member not found: " + memberId));
return borrowRecordRepository.findByMember(member.getId());
}
@Override
public List<BorrowRecord> getOverdueRecords() {
return borrowRecordRepository.findOverdueRecords();
}
// Fine operations
@Override
public double calculateMemberFines(String memberId) {
Member member = memberRepository.findByMemberId(memberId)
.orElseThrow(() -> new LibraryException("Member not found: " + memberId));
// Calculate fines for active overdue records
double totalFines = member.getOutstandingFines();
List<BorrowRecord> activeRecords = borrowRecordRepository.findByMember(member.getId())
.stream()
.filter(record -> record.getReturnDate() == null)
.filter(BorrowRecord::isOverdue)
.toList();
for (BorrowRecord record : activeRecords) {
record.calculateFine(dailyFineRate);
totalFines += record.getFineAmount();
}
return totalFines;
}
@Override
public PaymentResult payFine(String memberId, double amount) {
Member member = memberRepository.findByMemberId(memberId)
.orElseThrow(() -> new LibraryException("Member not found: " + memberId));
if (amount <= 0) {
return PaymentResult.failure("Payment amount must be positive");
}
double currentFines = member.getOutstandingFines();
if (amount > currentFines) {
return PaymentResult.failure("Payment amount exceeds outstanding fines");
}
member.payFine(amount);
return PaymentResult.success(amount, 
"Payment of $" + amount + " successful. Remaining fines: $" + member.getOutstandingFines());
}
// Reports
@Override
public LibraryReport generateReport() {
return new LibraryReport(
bookRepository.count(),
memberRepository.count(),
borrowRecordRepository.findActiveRecords().size(),
borrowRecordRepository.findOverdueRecords().size(),
memberRepository.findMembersWithFines().stream()
.mapToDouble(Member::getOutstandingFines)
.sum()
);
}
}
// Result classes
public class BorrowResult {
private final boolean success;
private final String message;
private final BorrowRecord record;
private BorrowResult(boolean success, String message, BorrowRecord record) {
this.success = success;
this.message = message;
this.record = record;
}
public static BorrowResult success(BorrowRecord record, String message) {
return new BorrowResult(true, message, record);
}
public static BorrowResult failure(String message) {
return new BorrowResult(false, message, null);
}
// Getters
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public BorrowRecord getRecord() { return record; }
}
public class ReturnResult {
private final boolean success;
private final String message;
private final BorrowRecord record;
private final double fineAmount;
private ReturnResult(boolean success, String message, BorrowRecord record, double fineAmount) {
this.success = success;
this.message = message;
this.record = record;
this.fineAmount = fineAmount;
}
public static ReturnResult success(BorrowRecord record, String message) {
return new ReturnResult(true, message, record, record.getFineAmount());
}
public static ReturnResult failure(String message) {
return new ReturnResult(false, message, null, 0);
}
// Getters
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public BorrowRecord getRecord() { return record; }
public double getFineAmount() { return fineAmount; }
}
public class PaymentResult {
private final boolean success;
private final String message;
private final double amountPaid;
private final double remainingFines;
private PaymentResult(boolean success, String message, double amountPaid, double remainingFines) {
this.success = success;
this.message = message;
this.amountPaid = amountPaid;
this.remainingFines = remainingFines;
}
public static PaymentResult success(double amountPaid, String message) {
return new PaymentResult(true, message, amountPaid, 0); // remaining calculated separately
}
public static PaymentResult failure(String message) {
return new PaymentResult(false, message, 0, 0);
}
// Getters
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public double getAmountPaid() { return amountPaid; }
public double getRemainingFines() { return remainingFines; }
}
// Report class
public class LibraryReport {
private final long totalBooks;
private final long totalMembers;
private final long currentlyBorrowed;
private final long overdueBooks;
private final double totalFines;
private final LocalDate generatedDate;
public LibraryReport(long totalBooks, long totalMembers, long currentlyBorrowed, 
long overdueBooks, double totalFines) {
this.totalBooks = totalBooks;
this.totalMembers = totalMembers;
this.currentlyBorrowed = currentlyBorrowed;
this.overdueBooks = overdueBooks;
this.totalFines = totalFines;
this.generatedDate = LocalDate.now();
}
// Getters
public long getTotalBooks() { return totalBooks; }
public long getTotalMembers() { return totalMembers; }
public long getCurrentlyBorrowed() { return currentlyBorrowed; }
public long getOverdueBooks() { return overdueBooks; }
public double getTotalFines() { return totalFines; }
public LocalDate getGeneratedDate() { return generatedDate; }
@Override
public String toString() {
return String.format(
"Library Report (%s)\n" +
"Total Books: %d\n" +
"Total Members: %d\n" +
"Currently Borrowed: %d\n" +
"Overdue Books: %d\n" +
"Total Outstanding Fines: $%.2f",
generatedDate, totalBooks, totalMembers, currentlyBorrowed, 
overdueBooks, totalFines
);
}
}
// Custom Exception
public class LibraryException extends RuntimeException {
public LibraryException(String message) {
super(message);
}
public LibraryException(String message, Throwable cause) {
super(message, cause);
}
}

4. Console User Interface

import java.util.*;
import java.time.Year;
public class LibraryConsoleUI {
private final LibraryService libraryService;
private final Scanner scanner;
public LibraryConsoleUI(LibraryService libraryService) {
this.libraryService = libraryService;
this.scanner = new Scanner(System.in);
}
public void start() {
System.out.println("=== Library Management System ===");
while (true) {
displayMainMenu();
int choice = getIntInput("Enter your choice: ");
switch (choice) {
case 1 -> manageBooks();
case 2 -> manageMembers();
case 3 -> manageBorrowing();
case 4 -> manageReturns();
case 5 -> generateReports();
case 6 -> manageFines();
case 0 -> {
System.out.println("Thank you for using Library Management System!");
return;
}
default -> System.out.println("Invalid choice. Please try again.");
}
}
}
private void displayMainMenu() {
System.out.println("\n--- Main Menu ---");
System.out.println("1. Manage Books");
System.out.println("2. Manage Members");
System.out.println("3. Borrow Books");
System.out.println("4. Return Books");
System.out.println("5. Reports");
System.out.println("6. Manage Fines");
System.out.println("0. Exit");
}
private void manageBooks() {
while (true) {
System.out.println("\n--- Book Management ---");
System.out.println("1. Add New Book");
System.out.println("2. Search Books by Title");
System.out.println("3. Search Books by Author");
System.out.println("4. View Available Books");
System.out.println("5. View All Books");
System.out.println("0. Back to Main Menu");
int choice = getIntInput("Enter your choice: ");
switch (choice) {
case 1 -> addNewBook();
case 2 -> searchBooksByTitle();
case 3 -> searchBooksByAuthor();
case 4 -> viewAvailableBooks();
case 5 -> viewAllBooks();
case 0 -> { return; }
default -> System.out.println("Invalid choice.");
}
}
}
private void addNewBook() {
System.out.println("\n--- Add New Book ---");
String isbn = getStringInput("ISBN: ");
String title = getStringInput("Title: ");
String author = getStringInput("Author: ");
String publisher = getStringInput("Publisher: ");
int publicationYear = getIntInput("Publication Year: ");
String genre = getStringInput("Genre: ");
int totalCopies = getIntInput("Total Copies: ");
try {
Book book = new Book(isbn, title, author, publisher, 
Year.of(publicationYear), genre, totalCopies);
libraryService.addBook(book);
System.out.println("Book added successfully!");
} catch (LibraryException e) {
System.out.println("Error: " + e.getMessage());
}
}
private void searchBooksByTitle() {
String title = getStringInput("Enter title to search: ");
List<Book> books = libraryService.searchBooksByTitle(title);
displayBooks(books);
}
private void searchBooksByAuthor() {
String author = getStringInput("Enter author to search: ");
List<Book> books = libraryService.searchBooksByAuthor(author);
displayBooks(books);
}
private void viewAvailableBooks() {
List<Book> books = libraryService.getAvailableBooks();
displayBooks(books);
}
private void viewAllBooks() {
// This would require adding a method to get all books
System.out.println("Feature not implemented in this version.");
}
private void displayBooks(List<Book> books) {
if (books.isEmpty()) {
System.out.println("No books found.");
return;
}
System.out.println("\n--- Books Found ---");
for (int i = 0; i < books.size(); i++) {
Book book = books.get(i);
System.out.printf("%d. %s\n", i + 1, book);
}
}
private void manageMembers() {
while (true) {
System.out.println("\n--- Member Management ---");
System.out.println("1. Register New Member");
System.out.println("2. Search Members by Name");
System.out.println("3. View Member Details");
System.out.println("0. Back to Main Menu");
int choice = getIntInput("Enter your choice: ");
switch (choice) {
case 1 -> registerNewMember();
case 2 -> searchMembersByName();
case 3 -> viewMemberDetails();
case 0 -> { return; }
default -> System.out.println("Invalid choice.");
}
}
}
private void registerNewMember() {
System.out.println("\n--- Register New Member ---");
String memberId = getStringInput("Member ID: ");
String firstName = getStringInput("First Name: ");
String lastName = getStringInput("Last Name: ");
String email = getStringInput("Email: ");
String phone = getStringInput("Phone: ");
System.out.println("Membership Types:");
for (Member.MembershipType type : Member.MembershipType.values()) {
System.out.printf("- %s (Max books: %d, Loan period: %d days)\n", 
type, type.getMaxBooks(), type.getLoanPeriodDays());
}
String typeInput = getStringInput("Membership Type: ").toUpperCase();
try {
Member.MembershipType membershipType = Member.MembershipType.valueOf(typeInput);
Member member = new Member(memberId, firstName, lastName, email, phone, membershipType);
libraryService.registerMember(member);
System.out.println("Member registered successfully!");
} catch (IllegalArgumentException e) {
System.out.println("Invalid membership type.");
} catch (LibraryException e) {
System.out.println("Error: " + e.getMessage());
}
}
private void searchMembersByName() {
String name = getStringInput("Enter name to search: ");
List<Member> members = libraryService.searchMembersByName(name);
displayMembers(members);
}
private void viewMemberDetails() {
String memberId = getStringInput("Enter Member ID: ");
Optional<Member> memberOpt = libraryService.findMemberById(memberId);
if (memberOpt.isPresent()) {
Member member = memberOpt.get();
System.out.println("\n--- Member Details ---");
System.out.println("ID: " + member.getMemberId());
System.out.println("Name: " + member.getFullName());
System.out.println("Email: " + member.getEmail());
System.out.println("Phone: " + member.getPhone());
System.out.println("Membership Type: " + member.getMembershipType());
System.out.println("Currently Borrowed: " + member.getCurrentlyBorrowedCount() + 
"/" + member.getMaxBooksAllowed());
System.out.println("Outstanding Fines: $" + member.getOutstandingFines());
// Show borrow history
List<BorrowRecord> history = libraryService.getMemberBorrowHistory(memberId);
if (!history.isEmpty()) {
System.out.println("\nBorrow History:");
for (BorrowRecord record : history) {
System.out.println(" - " + record.getBookCopy().getBook().getTitle() + 
" (Due: " + record.getDueDate() + 
", Returned: " + 
(record.getReturnDate() != null ? record.getReturnDate() : "Not yet") + ")");
}
}
} else {
System.out.println("Member not found.");
}
}
private void displayMembers(List<Member> members) {
if (members.isEmpty()) {
System.out.println("No members found.");
return;
}
System.out.println("\n--- Members Found ---");
for (int i = 0; i < members.size(); i++) {
Member member = members.get(i);
System.out.printf("%d. %s\n", i + 1, member);
}
}
private void manageBorrowing() {
System.out.println("\n--- Borrow Book ---");
String memberId = getStringInput("Member ID: ");
String isbn = getStringInput("Book ISBN: ");
BorrowResult result = libraryService.borrowBook(memberId, isbn);
if (result.isSuccess()) {
System.out.println("Success: " + result.getMessage());
} else {
System.out.println("Error: " + result.getMessage());
}
}
private void manageReturns() {
System.out.println("\n--- Return Book ---");
String memberId = getStringInput("Member ID: ");
String isbn = getStringInput("Book ISBN: ");
int copyNumber = getIntInput("Copy Number: ");
ReturnResult result = libraryService.returnBook(memberId, isbn, copyNumber);
if (result.isSuccess()) {
System.out.println("Success: " + result.getMessage());
} else {
System.out.println("Error: " + result.getMessage());
}
}
private void manageFines() {
while (true) {
System.out.println("\n--- Fine Management ---");
System.out.println("1. Calculate Member Fines");
System.out.println("2. Pay Fines");
System.out.println("0. Back to Main Menu");
int choice = getIntInput("Enter your choice: ");
switch (choice) {
case 1 -> calculateFines();
case 2 -> payFines();
case 0 -> { return; }
default -> System.out.println("Invalid choice.");
}
}
}
private void calculateFines() {
String memberId = getStringInput("Enter Member ID: ");
double fines = libraryService.calculateMemberFines(memberId);
System.out.printf("Total fines for member %s: $%.2f\n", memberId, fines);
}
private void payFines() {
String memberId = getStringInput("Enter Member ID: ");
double amount = getDoubleInput("Enter payment amount: ");
PaymentResult result = libraryService.payFine(memberId, amount);
if (result.isSuccess()) {
System.out.println("Success: " + result.getMessage());
} else {
System.out.println("Error: " + result.getMessage());
}
}
private void generateReports() {
System.out.println("\n--- Library Reports ---");
LibraryReport report = libraryService.generateReport();
System.out.println(report);
// Show overdue records
List<BorrowRecord> overdueRecords = libraryService.getOverdueRecords();
if (!overdueRecords.isEmpty()) {
System.out.println("\n--- Overdue Books ---");
for (BorrowRecord record : overdueRecords) {
System.out.printf("- %s borrowed by %s (Due: %s, Overdue: %d days)\n",
record.getBookCopy().getBook().getTitle(),
record.getMember().getFullName(),
record.getDueDate(),
record.getDaysOverdue());
}
}
}
// Utility methods for input
private String getStringInput(String prompt) {
System.out.print(prompt);
return scanner.nextLine().trim();
}
private int getIntInput(String prompt) {
while (true) {
try {
System.out.print(prompt);
return Integer.parseInt(scanner.nextLine().trim());
} catch (NumberFormatException e) {
System.out.println("Please enter a valid number.");
}
}
}
private double getDoubleInput(String prompt) {
while (true) {
try {
System.out.print(prompt);
return Double.parseDouble(scanner.nextLine().trim());
} catch (NumberFormatException e) {
System.out.println("Please enter a valid amount.");
}
}
}
}

5. Main Application Class

public class LibraryManagementSystem {
public static void main(String[] args) {
// Initialize repositories
BookRepository bookRepository = new BookRepository();
MemberRepository memberRepository = new MemberRepository();
BorrowRecordRepository borrowRecordRepository = new BorrowRecordRepository();
// Initialize service
LibraryService libraryService = new LibraryServiceImpl(
bookRepository, memberRepository, borrowRecordRepository);
// Initialize UI
LibraryConsoleUI ui = new LibraryConsoleUI(libraryService);
// Add some sample data
initializeSampleData(libraryService);
// Start the application
ui.start();
}
private static void initializeSampleData(LibraryService libraryService) {
try {
// Add sample books
libraryService.addBook(new Book("978-0134685991", "Effective Java", "Joshua Bloch", 
"Addison-Wesley", Year.of(2018), "Programming", 3));
libraryService.addBook(new Book("978-1617293290", "Spring in Action", "Craig Walls", 
"Manning", Year.of(2020), "Programming", 2));
libraryService.addBook(new Book("978-0596009205", "Head First Design Patterns", "Eric Freeman", 
"O'Reilly", Year.of(2020), "Programming", 4));
// Register sample members
libraryService.registerMember(new Member("MEM001", "John", "Doe", 
"[email protected]", "555-0101", Member.MembershipType.FACULTY));
libraryService.registerMember(new Member("MEM002", "Jane", "Smith", 
"[email protected]", "555-0102", Member.MembershipType.STUDENT));
System.out.println("Sample data initialized successfully!");
} catch (LibraryException e) {
System.out.println("Error initializing sample data: " + e.getMessage());
}
}
}

Features Included

  1. Book Management
  • Add new books with multiple copies
  • Search books by title, author, genre
  • Track available copies
  1. Member Management
  • Member registration with different membership types
  • Search members by name
  • View member details and borrowing history
  1. Borrowing System
  • Borrow books with validation checks
  • Automatic due date calculation based on membership type
  • Prevent over-borrowing and track fines
  1. Return System
  • Process book returns
  • Calculate overdue fines automatically
  • Update book availability
  1. Fine Management
  • Calculate outstanding fines
  • Process fine payments
  • Prevent borrowing when fines exist
  1. Reporting
  • Library statistics
  • Overdue books tracking
  • Financial reports

Key Design Patterns Used

  • Repository Pattern for data access
  • Service Layer Pattern for business logic
  • DTO Pattern for data transfer
  • Factory Pattern for object creation
  • Strategy Pattern for different membership types

This Library Management System provides a solid foundation that can be extended with database persistence, web interfaces, or additional features like reservations, renewals, and email notifications.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper