Java OOP Capstone Projects: 4 Production-Grade Class Systems
Project 1: Student Management System ยท Project 2: Book Library System ยท Project 3: Product Inventory Manager ยท Project 4: Bank Account Application ยท OOP Design Principles Review
Building 4 complete production-grade object-oriented Java systems applying all Phase 9 concepts: encapsulation, constructors, overloading, static members, toString(), and enums โ a Student Management System, a Book Library Catalog, a Product Inventory Manager, and a complete Bank Account Application with transaction history.
1. OOP System Design Principles Used in These Projects
Before building, let's review the engineering principles applied across all 4 capstone projects:
1. Encapsulation: All fields are private; exposed via validated getters/setters.
2. Constructor Overloading: Multiple constructors for flexible object creation.
3. Static Members: Class-level counters and constants shared across all objects.
4. toString() Override: Clean, readable object representations.
5. Enums for Status/Category: Type-safe status fields instead of fragile Strings.
6. Single Responsibility: Each class manages exactly one business concept.
Beginner Example & Code Anatomy
import java.util.Arrays;
// =====================================================================
// PROJECT 1: STUDENT MANAGEMENT SYSTEM
// =====================================================================
class Student {
private static int totalStudents = 0;
enum AcademicStatus { ACTIVE, ON_LEAVE, GRADUATED, EXPELLED }
private final String studentId;
private String name;
private int age;
private double gpa;
private AcademicStatus status;
Student(String name, int age, double gpa) {
totalStudents++;
this.studentId = String.format("STU-%04d", totalStudents);
setName(name);
setAge(age);
setGpa(gpa);
this.status = AcademicStatus.ACTIVE;
}
public static int getTotalStudents() { return totalStudents; }
public String getName() { return name; }
public double getGpa() { return gpa; }
public AcademicStatus getStatus() { return status; }
public void setName(String name) {
if (name == null || name.isBlank()) throw new IllegalArgumentException("Student name required!");
this.name = name.trim();
}
public void setAge(int age) {
if (age < 15 || age > 80) throw new IllegalArgumentException("Invalid age: " + age);
this.age = age;
}
public void setGpa(double gpa) {
if (gpa < 0.0 || gpa > 4.0) throw new IllegalArgumentException("GPA must be 0.0โ4.0");
this.gpa = gpa;
}
public void setStatus(AcademicStatus status) { this.status = status; }
@Override
public String toString() {
return String.format("[%s] %-18s | Age: %2d | GPA: %.2f | Status: %s",
studentId, name, age, gpa, status);
}
}
// =====================================================================
// PROJECT 2: BOOK LIBRARY SYSTEM
// =====================================================================
class Book {
enum Genre { FICTION, NON_FICTION, SCIENCE, TECHNOLOGY, HISTORY, BIOGRAPHY }
private final String isbn;
private String title;
private String author;
private double price;
private int availableCopies;
private Genre genre;
Book(String isbn, String title, String author, double price, int copies, Genre genre) {
this.isbn = isbn;
this.title = title;
this.author = author;
this.price = price;
this.availableCopies = copies;
this.genre = genre;
}
public boolean isAvailable() { return availableCopies > 0; }
public String getTitle() { return title; }
public String getAuthor() { return author; }
public int getAvailableCopies() { return availableCopies; }
public boolean checkOut() {
if (!isAvailable()) return false;
availableCopies--;
return true;
}
public void returnBook() { availableCopies++; }
@Override
public String toString() {
return String.format("[%s] %-30s by %-18s | Genre: %-11s | Copies: %d | Available: %b",
isbn, title, author, genre, availableCopies, isAvailable());
}
}
// =====================================================================
// PROJECT 3: PRODUCT INVENTORY MANAGER
// =====================================================================
class Product {
enum Category { ELECTRONICS, CLOTHING, FOOD, SPORTS, FURNITURE }
private static int productCount = 0;
private final String productId;
private String name;
private double price;
private int stock;
private Category category;
Product(String name, double price, int stock, Category category) {
productCount++;
this.productId = String.format("PRD-%03d", productCount);
this.name = name;
this.price = price;
this.stock = stock;
this.category = category;
}
public static int getProductCount() { return productCount; }
public String getName() { return name; }
public double getPrice() { return price; }
public int getStock() { return stock; }
public boolean sellUnits(int qty) {
if (qty <= 0 || qty > stock) return false;
stock -= qty;
return true;
}
public void restock(int qty) {
if (qty > 0) stock += qty;
}
@Override
public String toString() {
return String.format("[%s] %-25s | $%7.2f | Stock: %3d | Category: %s",
productId, name, price, stock, category);
}
}
// =====================================================================
// PROJECT 4: BANK ACCOUNT APPLICATION
// =====================================================================
class BankAccount {
enum AccountType { SAVINGS, CURRENT, FIXED_DEPOSIT }
private static int accountSerial = 1000;
private final String accountNumber;
private String holderName;
private double balance;
private AccountType type;
private int transactionCount;
BankAccount(String holderName, double initialDeposit, AccountType type) {
this.accountNumber = "ACC-" + (++accountSerial);
this.holderName = holderName;
this.balance = Math.max(0, initialDeposit);
this.type = type;
this.transactionCount = 1;
}
public String getAccountNumber() { return accountNumber; }
public double getBalance() { return balance; }
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("Deposit must be positive!");
balance += amount;
transactionCount++;
System.out.printf(" + DEPOSIT $%8.2f | New Balance: $%10.2f%n", amount, balance);
}
public void withdraw(double amount) {
if (amount <= 0 || amount > balance)
throw new IllegalArgumentException("Invalid withdrawal: $" + amount);
balance -= amount;
transactionCount++;
System.out.printf(" - WITHDRAW $%8.2f | New Balance: $%10.2f%n", amount, balance);
}
public static boolean transfer(BankAccount from, BankAccount to, double amount) {
if (from.balance < amount) return false;
from.balance -= amount;
to.balance += amount;
from.transactionCount++;
to.transactionCount++;
return true;
}
@Override
public String toString() {
return String.format("[%s] %-15s | Type: %-14s | Balance: $%10.2f | Txns: %d",
accountNumber, holderName, type, balance, transactionCount);
}
}
// =====================================================================
// MAIN: ORCHESTRATE ALL 4 SYSTEMS
// =====================================================================
public class Main {
public static void main(String[] args) {
System.out.println("========================================");
System.out.println(" PROJECT 1: STUDENT MANAGEMENT SYSTEM");
System.out.println("========================================");
Student stu1 = new Student("Ravi Kumar", 20, 3.85);
Student stu2 = new Student("Priya Sharma", 22, 3.72);
Student stu3 = new Student("Kiran Reddy", 21, 3.90);
stu2.setStatus(Student.AcademicStatus.ON_LEAVE);
System.out.println(stu1);
System.out.println(stu2);
System.out.println(stu3);
System.out.println("Total Enrolled Students: " + Student.getTotalStudents());
System.out.println("
========================================");
System.out.println(" PROJECT 2: BOOK LIBRARY SYSTEM");
System.out.println("========================================");
Book b1 = new Book("978-001", "Clean Code", "Robert C. Martin", 35.99, 3, Book.Genre.TECHNOLOGY);
Book b2 = new Book("978-002", "The Pragmatic Programmer","Andy Hunt", 42.50, 2, Book.Genre.TECHNOLOGY);
Book b3 = new Book("978-003", "Effective Java", "Joshua Bloch", 40.00, 1, Book.Genre.TECHNOLOGY);
System.out.println(b1);
System.out.println(b2);
System.out.println(b3);
System.out.println("
Checking out 'Effective Java': " + b3.checkOut());
System.out.println("Try checkout again (no copies): " + b3.checkOut());
System.out.println("After return:");
b3.returnBook();
System.out.println(b3);
System.out.println("
========================================");
System.out.println(" PROJECT 3: PRODUCT INVENTORY MANAGER");
System.out.println("========================================");
Product p1 = new Product("Mechanical Keyboard", 79.99, 150, Product.Category.ELECTRONICS);
Product p2 = new Product("Wireless Mouse", 29.99, 320, Product.Category.ELECTRONICS);
Product p3 = new Product("Yoga Mat", 19.99, 80, Product.Category.SPORTS);
System.out.println(p1);
System.out.println(p2);
System.out.println(p3);
System.out.println("
Selling 5 units of '" + p1.getName() + "': " + p1.sellUnits(5));
p3.restock(50);
System.out.println("After restock & sell:");
System.out.println(p1);
System.out.println(p3);
System.out.println("Total Products in Catalog: " + Product.getProductCount());
System.out.println("
========================================");
System.out.println(" PROJECT 4: BANK ACCOUNT APPLICATION");
System.out.println("========================================");
BankAccount alice = new BankAccount("Alice Sharma", 10000.0, BankAccount.AccountType.SAVINGS);
BankAccount bob = new BankAccount("Bob Reddy", 5000.0, BankAccount.AccountType.CURRENT);
System.out.println("Initial State:");
System.out.println(" " + alice);
System.out.println(" " + bob);
System.out.println("
Alice's Transactions:");
alice.deposit(2500.0);
alice.withdraw(800.0);
System.out.println("
Transfer $3000 from Alice to Bob: "
+ BankAccount.transfer(alice, bob, 3000.0));
System.out.println("
Final State:");
System.out.println(" " + alice);
System.out.println(" " + bob);
}
}
๐ Line-by-Line Code Explanation
Student.getTotalStudents()
Static method accesses the shared static counter tracking all Student objects created across the JVM.
b3.checkOut()
Instance method with guard: decrements availableCopies only if copies > 0, returning boolean success.
BankAccount.transfer(alice, bob, 3000.0)
Static method receives both account references and atomically deducts from sender and credits receiver.
System.out.println(stu1)
Implicitly calls Student.toString() to display the formatted student profile string.
Practical Real-World Example
public class PracticalApplication {
public static void main(String[] args) {
// Rapid object creation and OOP showcase
System.out.println("=== 3-Second OOP Showcase ===");
// Polymorphic object creation
Student[] batch = {
new Student("Ravi", 19, 3.5),
new Student("Priya", 20, 3.8),
new Student("Kiran", 21, 3.9)
};
double totalGpa = 0;
for (Student s : batch) {
totalGpa += s.getGpa();
System.out.println(" " + s);
}
System.out.printf("Batch Average GPA: %.2f%n", totalGpa / batch.length);
}
}
- Making business methods
staticwhen they depend on instance state (likewithdraw()). - Forgetting to make IDs
finalso they cannot be accidentally reassigned after construction. - Not incrementing static counters inside constructors, causing incorrect headcount tracking.
- Using public fields instead of private fields + getters, breaking encapsulation.
Test your understanding by writing the code directly in your editor or running in our online Java compiler:
// Coding Challenge:
// Extend the BankAccount with a fixed deposit calculation:
// 1. Add method: double calculateMaturity(int years, double annualRate)
// that returns: balance * Math.pow(1 + annualRate/100, years)
// 2. Test it on a FIXED_DEPOSIT account.
class ExtendedBankAccount extends BankAccount {
ExtendedBankAccount(String holder, double deposit, AccountType type) {
super(holder, deposit, type);
}
public double calculateMaturity(int years, double annualRate) {
return getBalance() * Math.pow(1 + annualRate / 100.0, years);
}
}
public class Challenge {
public static void main(String[] args) {
ExtendedBankAccount fd = new ExtendedBankAccount("Savings Plan", 50000, BankAccount.AccountType.FIXED_DEPOSIT);
System.out.printf("After 5 years at 7.5%%: $%.2f%n", fd.calculateMaturity(5, 7.5));
}
}
๐ก Frequently Asked Questions & Interview Insights
โ When should I use an array of objects vs ArrayList for storing multiple objects?
Use a plain array when the size is known and fixed (e.g. seating chart). Use ArrayList when items need to be dynamically added or removed. We will explore ArrayList deeply in Phase 15: Collections Framework.
โ How do I compare two Student objects by GPA?
Implement the `Comparable
โ What is the difference between `null` and an object with all default field values?
`null` means no object exists at all โ the reference points to nothing. A default object (`new Student()`) exists in Heap memory with fields set to Java defaults (0, false, null).
๐ Quick Chapter Recap
- Classes bundle private state (fields) and public behavior (methods) into cohesive units.
- Static auto-generated IDs (e.g.
"STU-" + ++counter) reliably produce unique identifiers per object. - Enums provide type-safe status codes preventing typo-based bugs.
toString()override enables objects to print meaningful information in logs and console.- All 4 pillars of OOP (Encapsulation, Abstraction, Inheritance, Polymorphism) build upon these fundamentals.