Java Student Management System
Table of Contents + β
In the last lesson you built a Java calculator application. Now letβs build a Student Management System, a classic console app to add, view, search, update, and remove students. We will build it slowly, one method at a time, so you can type along.
π― What we are building
Picture a small school office. People enrol, get looked up, get their marks fixed, and leave. Each action is a small separate job. Our program does them all through a text menu.
This is a CRUD app. CRUD stands for Create, Read, Update, Delete. Almost every app sits on top of it:
- Contacts app: create, read, update, delete contacts.
- Shopping cart: same, but with items.
- Learn it once here and you see it everywhere.
What our app lets the user do:
- Add a new student (id, name, marks).
- View all students.
- Search for a student by id.
- Search for a student by name.
- Update a studentβs marks.
- Remove a student.
- Exit.
We combine three building blocks:
- A
Studentclass holds one studentβs data. - An
ArrayListholds many students and resizes itself. - A
do-whilemenu loop keeps showing options until the user quits.
Letβs start with the data.
π§± Step 1: design the Student class
First we need a clear shape for what a student is. A class bundles related data into one object.
Here is the Student class with fields, a constructor, getters, a setter for marks, and a toString.
class Student { private int id; // unique number for each student private String name; private double marks;
public Student(int id, String name, double marks) { this.id = id; this.name = name; this.marks = marks; }
public int getId() { return id; } public String getName() { return name; } public double getMarks() { return marks; }
public void setMarks(double marks) { // β
marks can change, id and name stay fixed this.marks = marks; }
@Override public String toString() { return "ID " + id + " | " + name + " | marks: " + marks; }}The parts:
- The three fields are
private. That is encapsulation: outside code cannot change them directly. It must go through our methods. - The constructor takes id, name, and marks and stores them. Every
Studentstarts with all three set. - The getters let other code read the values safely.
- There is a
setMarksbut nosetIdorsetName. An id and name should never change. Only marks can, so only marks gets a setter. toStringbuilds one tidy line. Java calls it automatically when we print aStudent, so the list prints nicely.
The id matters. Two students can share a name. An id is unique, so it points at exactly one record. We use it for updating and removing.
π¦ Step 2: store students in an ArrayList
We need a container whose size changes as people are added and removed. A plain array has a fixed size, so it is a poor fit. An ArrayList resizes itself.
This line creates the list that holds every student.
import java.util.ArrayList;
ArrayList<Student> students = new ArrayList<>();What to notice:
ArrayList<Student>means βa list that holdsStudentobjects only.β The compiler blocks the wrong type.- It starts empty. We add and remove as the user works. We never set a size.
- We keep it in
mainand pass it to each method, so every part shares the same list.
π½οΈ Step 3: the menu loop
The heart of the app is a menu that shows again and again until the user exits. A do-while loop fits, because the menu should show at least once before we check whether to stop.
Here is the skeleton of main. The operations are method calls for now. We write each one next.
import java.util.ArrayList;import java.util.Scanner;
public class StudentManagementSystem { public static void main(String[] args) { ArrayList<Student> students = new ArrayList<>(); Scanner scanner = new Scanner(System.in); int choice;
do { printMenu(); choice = readInt(scanner, "Choose an option: ");
switch (choice) { case 1: addStudent(students, scanner); break; case 2: viewAll(students); break; case 3: searchById(students, scanner); break; case 4: searchByName(students, scanner); break; case 5: updateMarks(students, scanner); break; case 6: removeStudent(students, scanner); break; case 7: System.out.println("Goodbye!"); break; default: System.out.println("Invalid option. Pick 1 to 7."); } } while (choice != 7);
scanner.close(); }}Each time around the loop:
printMenu()shows the options.readIntreads the choice as a number. A small helper keeps input handling in one place.- The
switchcalls the matching method. Each method gets thestudentslist and thescanner. - The loop runs until
choiceis 7, then stops and closes the scanner.
Notice how main says what happens, not how. Each βhowβ lives in its own method: one method, one job.
Here is printMenu, just a block of prints.
static void printMenu() { System.out.println("\n--- Student Management ---"); System.out.println("1. Add student"); System.out.println("2. View all students"); System.out.println("3. Search by id"); System.out.println("4. Search by name"); System.out.println("5. Update marks"); System.out.println("6. Remove student"); System.out.println("7. Exit");}Now we build the operations one at a time.
β Step 4a: add a student
Read an id, a name, and marks, then create a Student and add it to the list.
static void addStudent(ArrayList<Student> students, Scanner scanner) { int id = readInt(scanner, "Enter id: ");
// β Guard against a duplicate id before adding if (findById(students, id) != null) { System.out.println("An id like that already exists. Try another."); return; }
System.out.print("Enter name: "); String name = scanner.nextLine();
double marks = readDouble(scanner, "Enter marks: ");
students.add(new Student(id, name, marks)); // β
create the object, store it System.out.println("Student added.");}The parts:
- We read the id first and check it is not taken. A duplicate id would break searching and updating later.
findById(written below) returns the matching student ornull.nullmeans βno match.β- We read the name with
nextLine, then the marks with another helper. students.add(new Student(...))builds and stores the object in one line.returnafter the duplicate message ends the method early, so we never add a bad record.
π Step 4b: view all students
Viewing is the βReadβ in CRUD. Loop over the list and print each student. Handle the empty case so the user sees a message instead of blank space.
static void viewAll(ArrayList<Student> students) { if (students.isEmpty()) { System.out.println("No students yet."); return; } System.out.println("All students:"); for (Student s : students) { System.out.println("- " + s); // β
toString runs automatically }}The parts:
students.isEmpty()catches the empty list. We print a message and return early.- The for-each loop visits each
Student. - Printing
scalls ourtoString, so each line comes out neatly. No digging out fields by hand.
π Step 4c: search by id and by name
Searching is where the id pays off:
- An id is unique, so searching by id finds at most one match.
- A name can repeat, so searching by name might find several. We collect all matches.
First, a shared helper that finds a student by id. Many methods reuse it.
static Student findById(ArrayList<Student> students, int id) { for (Student s : students) { if (s.getId() == id) { return s; // β
found it, hand it back } } return null; // β no match, signal "nothing found"}This helper returns the first student whose id matches, or null if none match. null is the common Java way to say βfound nothing.β The caller checks for it and reacts.
Now search by id, which uses that helper.
static void searchById(ArrayList<Student> students, Scanner scanner) { int id = readInt(scanner, "Enter id to find: "); Student found = findById(students, id); if (found == null) { System.out.println("No student with id " + id + "."); } else { System.out.println("Found: " + found); }}The parts:
- Read an id from the user.
- Call
findById, which gives back aStudentornull. nullmeans tell the user nothing was found. Otherwise print the match.
Search by name is different. Names can repeat, so we check every student and print each match.
static void searchByName(ArrayList<Student> students, Scanner scanner) { System.out.print("Enter name to find: "); String target = scanner.nextLine(); boolean found = false; for (Student s : students) { // β
equalsIgnoreCase compares text and ignores capitals if (s.getName().equalsIgnoreCase(target)) { System.out.println("Found: " + s); found = true; } } if (!found) { System.out.println("No student named " + target + "."); }}The parts:
- We compare names with
equalsIgnoreCase, not==.==checks whether two variables point to the same object, which we do not want.equalsIgnoreCasechecks the text and treats βalexβ and βAlexβ as a match. - A
foundflag startsfalseand flips totrueon any match. If it is stillfalseafter the loop, nothing matched.
βοΈ Step 4d: update a studentβs marks
Updating is the βUβ in CRUD. Find the student by id, then change marks through setMarks. This is why the field is private with a setter: the change goes through one controlled door.
static void updateMarks(ArrayList<Student> students, Scanner scanner) { int id = readInt(scanner, "Enter id to update: "); Student s = findById(students, id); if (s == null) { System.out.println("No student with id " + id + "."); return; } double newMarks = readDouble(scanner, "Enter new marks: "); s.setMarks(newMarks); // β
update through the setter System.out.println("Updated: " + s);}The parts:
- Read the id and reuse
findById. No need to write the loop again. - If no student is found, say so and return early.
- Otherwise read the new marks and call
setMarks.spoints to the real object in the list, so the list updates too. There is one object, and we hold a handle to it.
ποΈ Step 4e: remove a student
Removing is the βDβ in CRUD. Delete the student whose id matches, then check whether anything was actually removed so we give honest feedback.
static void removeStudent(ArrayList<Student> students, Scanner scanner) { int id = readInt(scanner, "Enter id to remove: "); // removeIf deletes every element that matches the test boolean removed = students.removeIf(s -> s.getId() == id); if (removed) { System.out.println("Student removed."); } else { System.out.println("No student with id " + id + "."); }}The parts:
removeIfremoves any element where the test is true. Here the test is βthis studentβs id equals the id the user typed.βremoveIfreturnstrueif it removed anything,falseif not. We use that to report honestly instead of always claiming success.
β¨οΈ Step 5: input handling and the Scanner pitfall
Reading from a Scanner has one trap that catches almost everyone:
nextInt()reads the number but leaves the Enter key (a newline) in the input.- The next
nextLine()reads that leftover newline and returns an empty string. - It looks like your program skipped a question.
Small helper methods read a number cleanly and fix this in one place.
static int readInt(Scanner scanner, String prompt) { System.out.print(prompt); while (!scanner.hasNextInt()) { // β guard against non-number input scanner.next(); // throw away the bad token System.out.print("Please enter a whole number. " + prompt); } int value = scanner.nextInt(); scanner.nextLine(); // β
clear the leftover newline return value;}
static double readDouble(Scanner scanner, String prompt) { System.out.print(prompt); while (!scanner.hasNextDouble()) { scanner.next(); System.out.print("Please enter a number. " + prompt); } double value = scanner.nextDouble(); scanner.nextLine(); // β
clear the leftover newline return value;}Why these helpers are worth it:
- The
scanner.nextLine()after reading a number clears the leftover newline. Inside the helper, we never forget it. - The
while (!scanner.hasNextInt())loop checks the input is a number first. Type βabcβ and we throw it away and ask again, instead of crashing. - Every place that needs a number calls
readIntorreadDouble, so input handling stays consistent.
The classic Scanner gotcha
After nextInt() or nextDouble(), a newline stays in the input. The next nextLine() reads it as an empty string and seems to skip a question. The fix is a single scanner.nextLine() right after reading the number. Keeping that fix inside a helper method means you write it once and never miss it.
π§© The finished structure
Putting it together, your file has this shape. The Student class sits beside StudentManagementSystem, and main calls the operation methods.
StudentManagementSystem.javaβββ class Studentβ βββ fields: id, name, marksβ βββ constructorβ βββ getId / getName / getMarksβ βββ setMarksβ βββ toStringβββ class StudentManagementSystem βββ main (the do-while menu loop + switch) βββ printMenu βββ addStudent βββ viewAll βββ searchById βββ searchByName βββ updateMarks βββ removeStudent βββ findById (shared helper) βββ readInt (input helper) βββ readDouble (input helper)Each method does one job, main ties them together, and Student holds the data. That separation makes the program easy to read and grow.
βΆοΈ Step 6: a full sample run
Letβs run it end to end: add two students, view, search, update marks, remove one, and exit.
Output
--- Student Management ---1. Add student2. View all students3. Search by id4. Search by name5. Update marks6. Remove student7. ExitChoose an option: 1Enter id: 101Enter name: AlexEnter marks: 78Student added.
--- Student Management ---Choose an option: 1Enter id: 102Enter name: RiyaEnter marks: 91Student added.
--- Student Management ---Choose an option: 2All students:- ID 101 | Alex | marks: 78.0- ID 102 | Riya | marks: 91.0
--- Student Management ---Choose an option: 4Enter name to find: riyaFound: ID 102 | Riya | marks: 91.0
--- Student Management ---Choose an option: 5Enter id to update: 101Enter new marks: 85Updated: ID 101 | Alex | marks: 85.0
--- Student Management ---Choose an option: 6Enter id to remove: 102Student removed.
--- Student Management ---Choose an option: 2All students:- ID 101 | Alex | marks: 85.0
--- Student Management ---Choose an option: 7Goodbye!Each option maps to one method, and the same students list carries data between choices. Add changes it, view reads it, update edits in place, remove takes one out. That is CRUD running live.
π οΈ Practice Challenge
Try extending the project before checking the answers. Each builds on a method you already have.
Challenge 1: Add a menu option that prints the class average marks. Handle the empty list so you never divide by zero.
Challenge 2: Add an option that finds and prints the top student, the one with the highest marks.
β οΈ Common Mistakes
A few project slip-ups to watch for.
- Forgetting to clear the Scanner newline. After
nextInt()ornextDouble(), addscanner.nextLine()before reading a String, or the text input gets skipped. Putting it in a helper solves it once. - Not handling an empty list. Viewing, searching, averaging, or finding the top student with no records should show a friendly message, not blank output or a crash.
- Comparing names with
==. Use.equals()or.equalsIgnoreCase()to compare the text.==checks whether two variables point to the same object, which is rarely what you mean for text. - Letting duplicate ids in. If two students share an id, search, update, and remove get confused about which one you mean. Check for a duplicate id before adding.
- Assuming a remove always worked.
removeIfmight match nothing. Check itstrue/falseresult and tell the user honestly.
β Best Practices
Habits that make console projects easy to read and grow.
- Use a class for your data. A
Studentclass keeps each recordβs fields together and protects them with encapsulation. - Use an ArrayList for the collection. It resizes as records are added and removed, so you never manage a fixed size.
- Give each operation its own method.
addStudent,viewAll,searchById, and the rest each do one job.mainstays short and readable. - Share helpers, do not repeat code.
findById,readInt, andreadDoubleare written once and reused everywhere. - Validate input and guard edge cases. Handle empty lists, non-number input, duplicate ids, and the Scanner newline issue from the start.
π§© What Youβve Learned
Nicely done. You built a real, complete app from the ground up. Letβs recap.
- β A Student Management System is a CRUD app: Create, Read, Update, Delete.
- β
A
Studentclass holds each recordβs data (id, name, marks) using encapsulation, a constructor, getters, a setter, andtoString. - β
An
ArrayListstores the students and resizes as they are added or removed. - β
A
do-whilemenu loop with aswitchdrives the whole program, calling one method per option. - β
A shared
findByIdhelper powers search, update, and remove without repeating the loop. - β
Real touches matter: clearing the Scanner newline, validating input, guarding empty lists, blocking duplicate ids, and comparing names with
equalsIgnoreCase.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
Why use an ArrayList to store the students?
Why: An ArrayList resizes automatically, which suits a list that grows and shrinks.
- 2
Which loop is ideal for a menu that must show at least once?
Why: A do-while runs its body before checking, so the menu always shows at least once.
- 3
Why call scanner.nextLine() after nextInt()?
Why: nextInt() leaves a newline that would make the next nextLine() read an empty string.
- 4
How should you compare a searched name to a student's name?
Why: Use equals/equalsIgnoreCase to compare the text; == compares references.
π Whatβs Next?
Next we build another real project that organises records and applies CRUD on a larger scale. Letβs build it.