Java Mini Projects
You can read every page of a swimming guide and still sink the first time you jump in. Knowing what loops, classes, collections and threads are isn't the same as building something with them, where you must choose which ideas to use, connect them, and fix what breaks. Small projects are where that happens. They are also what interviewers ask about: "Tell me about something you built" is one of the most common interview questions. In this lesson, you will find five mini projects in increasing difficulty, each with its features, suggested classes and the lessons to revisit, plus one complete starter project you can run and extend.
How to Approach a Project
- Write the features as a list before any code. Start with the smallest version that works.
- Sketch the classes: the nouns in your feature list (Student, Book, Loan) usually become records or classes.
- Build one feature at a time, and run the program after each one.
- Add tests for the logic (fee rules, due dates) once it works.
- Write a short README: what it does, how to run it, and what you would add next. Put it on GitHub.
Project 1: Class Fee Report (Starter, Complete)
Goal: read students and their fee payments, apply late fines and sibling discounts, and print a formatted report with totals and defaulters.
Skills: records, collections, streams, switch expressions, printf.
Let's see the complete first version. Run it, read it, then extend it with the ideas below.
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class FeeReport {
enum Status { PAID, LATE, UNPAID }
record Payment(String student, String className, int baseFee,
Status status, boolean hasSibling) {
int amountDue() {
int fee = switch (status) {
case PAID, UNPAID -> baseFee;
case LATE -> baseFee + 500;
};
return hasSibling ? fee - fee / 10 : fee;
}
}
public static void main(String[] args) {
List<Payment> payments = List.of(
new Payment("Navika", "10-A", 15000, Status.PAID, true),
new Payment("Srihitha", "10-A", 15000, Status.LATE, false),
new Payment("Advitha", "9-B", 14000, Status.UNPAID, true),
new Payment("Navika Anamaneni", "9-B", 14000,
Status.PAID, false));
System.out.printf("%-18s %-6s %-7s %8s%n",
"Student", "Class", "Status", "Due");
System.out.println("-".repeat(42));
payments.stream()
.sorted(Comparator.comparing(Payment::className)
.thenComparing(Payment::student))
.forEach(p -> System.out.printf("%-18s %-6s %-7s %,8d%n",
p.student(), p.className(), p.status(), p.amountDue()));
Map<String, Integer> byClass = payments.stream()
.collect(Collectors.groupingBy(Payment::className, TreeMap::new,
Collectors.summingInt(Payment::amountDue)));
System.out.println("-".repeat(42));
byClass.forEach((c, total) ->
System.out.printf("Total for %-5s %,26d%n", c, total));
List<String> defaulters = payments.stream()
.filter(p -> p.status() == Status.UNPAID)
.map(Payment::student)
.toList();
System.out.println("Defaulters: " + defaulters);
}
}Student Class Status Due
------------------------------------------
Navika 10-A PAID 13,500
Srihitha 10-A LATE 15,500
Advitha 9-B UNPAID 12,600
Navika Anamaneni 9-B PAID 14,000
------------------------------------------
Total for 10-A 29,000
Total for 9-B 26,600
Defaulters: [Advitha]Explanation: a record holds each payment and knows how to compute what is due, using a switch expression for the late fine and a 10 percent sibling discount. Streams sort by class and name, group totals by class into a TreeMap, and filter the defaulters.
Extend it:
- Read payments from a CSV file with
Files.readAllLinesinstead ofList.of. - Add a
PARTIALstatus with an amount paid, and show the balance. - Write the report to a file, and add JUnit tests for
amountDue.
Project 2: Expense Tracker
Goal: a command-line tool to add expenses, list them, filter by category or month, and show totals per category, saved to a file between runs.
| Part | Suggested design | Lessons to revisit |
|---|---|---|
| Data | record Expense(LocalDate date, String category, BigDecimal amount, String note) | "Records in Java", "LocalDate, LocalTime and LocalDateTime" |
| Storage | Save and load as CSV with Files.writeString and Files.readAllLines | "NIO.2 Path and Files Classes" |
| Reports | groupingBy(Expense::category, summingDouble(...)) or reducing with BigDecimal | "Stream API in Java" |
| Menu | A Scanner loop with a switch on the user's choice | "Scanner Class", "Switch Expressions" |
| Errors | Custom InvalidExpenseException for bad input | "Custom Exceptions in Java" |
Project 3: Library Management System
Goal: manage books, members and loans: issue and return books, calculate fines for late returns, search by title or author, and flag overdue loans every day.
| Part | Suggested design | Lessons to revisit |
|---|---|---|
| Domain | Book, Member, Loan classes; a LibraryService with the rules | "Classes and Objects", "SOLID Principles in Java" |
| Persistence | JDBC with an embedded database such as H2, using transactions for issue and return | "JDBC in Java" |
| Rules | Maximum 3 books per member; ₹5 fine per late day | "Writing Readable Methods and Classes" |
| Scheduling | A ScheduledExecutorService that checks overdue loans every day | "ScheduledExecutorService in Java" |
| Design | Repository interfaces so the service doesn't depend on JDBC directly | "Design Patterns in Java" |
Project 4: Online Exam System
Goal: an admin creates questions and exams; students log in, take a timed exam, and see their score and correct answers.
| Part | Suggested design | Lessons to revisit |
|---|---|---|
| Users | Two roles, admin and student; passwords stored as salted hashes | "Hashing with MessageDigest in Java" |
| Questions | A sealed interface Question with MultipleChoice and TrueFalse records | "Sealed Classes and Interfaces in Java" |
| Marking | Pattern matching switch over question types | "Pattern Matching for Switch in Java" |
| Timer | A scheduled task that ends the exam when time is up | "ScheduledExecutorService in Java" |
| Results | Store attempts with JDBC; report averages per question with streams | "JDBC in Java" |
Project 5: Multi-Client Chat Server
Goal: a server that many clients connect to over the network; messages from one client are broadcast to all, with nicknames and a /quit command.
| Part | Suggested design | Lessons to revisit |
|---|---|---|
| Networking | ServerSocket accepting clients; one handler per connection | "Socket Programming in Java" |
| Concurrency | One virtual thread per client: Executors.newVirtualThreadPerTaskExecutor() | "Virtual Threads in Java" |
| Shared state | A ConcurrentHashMap of nickname to client writer | "Concurrent Collections in Java" |
| Protocol | Text lines; commands start with / | "Java I/O Streams" |
| Shutdown | Close sockets with try-with-resources; handle disconnects cleanly | "Try-With-Resources in Java" |
Interview Corner
How do you present a project in an interview?
In about two minutes: the problem it solves, the main design decision and why (for example, "a sealed interface for question types so marking is checked by the compiler"), one difficulty you hit and how you fixed it, and what you would improve next.
What follow-up questions should you expect?
Why you chose a particular collection or class design, how you handled errors and invalid input, how you would test it, and what would change with many users at once (thread safety, database transactions).
Practice
- Easy: Extend
FeeReportwith aPARTIALstatus and anamountPaidfield, and print the balance for partially paid students. - Harder: Build Project 2, the expense tracker, with at least add, list, total-by-category and save/load. Write a README with sample commands and output.
Key Takeaways
Was this tutorial helpful?