Overview
A student management system is the classic first project for seeing why Java bundles data and behavior together in a class instead of scattering related values across separate arrays. A Student is not just an id, a name, a course, and a GPA sitting next to each other in memory — it is one coherent object that should be created, read, and changed as a single unit. That is exactly what a Java class gives you: private fields that hold a student's data, and public methods that are the only sanctioned way to read or change it.
By the end of this tutorial you will have a console application built around two classes: Student, which models one record, and StudentManager, which owns an ArrayList<Student> and provides add, search, update, and delete operations over it. You will see why Java programmers reach for List<Student> typed to the ArrayList implementation almost by default — it grows automatically as students are added, and its removeIf, enhanced-for, and index-based methods cover everything a menu-driven console app like this one needs without any manual array resizing.
- A
Studentclass with privateid,name,course, andgpafields plus getters/setters. - A
StudentManagerclass that owns anArrayList<Student>and assigns unique ids automatically. - A linear
findById()search reused by the update and delete operations. - An
updateStudent()method that safely edits an existing record found by id. - A
deleteStudent()method built onArrayList.removeIf(). - A
Scanner-based menu loop tying every operation together into one running program.
Prerequisites
- Classes and objects — defining a
classwithprivatefields, a constructor, andpublicmethods. - Collections basics —
java.util.ArrayListand theListinterface, plus the enhancedforloop. - Console input — reading lines and numbers with
java.util.Scanner. - Method basics — parameters, return types, and calling one method from another.
- The
thiskeyword — disambiguating a field name from a constructor or setter parameter of the same name.
Project Structure
The whole program lives in a single file, StudentManagementSystem.java, containing three top-level types: Student, StudentManager, and the public class StudentManagementSystem that holds main(). Student is a pure data-and-behavior class — it never touches the console or the collection it lives in. StudentManager is the layer above it: it owns the ArrayList<Student>, hands out ids, and exposes one method per menu action (addStudent, findById, updateStudent, deleteStudent, getAll). main() never manipulates the list directly; it only ever calls methods on a single shared StudentManager instance, which keeps every rule about how students are created or removed enforced in exactly one place.
Only one Java source file may declare a public top-level class per file, and it must match the filename — that is why Student and StudentManager below are declared without the public modifier (giving them default, package-private visibility), while StudentManagementSystem, the one containing main(), is the public class that matches the file name.
Step 1: Define the Student Class
Every field starts private, which is what makes this a proper encapsulated class rather than a loose bag of public variables — code outside Student can only reach these fields through the getters and setters defined here. The constructor uses this.field = parameter throughout, since the parameter names deliberately shadow the field names for readability; this.id always refers to the field, plain id always refers to the parameter.
// Represents one student record. Fields are private so a Student can only be// created or modified through the constructor, setters, and StudentManager// below — never by reaching directly into its data from outside the class.class Student { private int id; // Assigned by StudentManager, never chosen by the caller private String name; // Student's full name private String course; // Course or major the student is enrolled in private double gpa; // Grade point average; a double so partial grades like 3.75 are representable
public Student(int id, String name, String course, double gpa) { this.id = id; // "this." picks the field over the same-named constructor parameter this.name = name; this.course = course; this.gpa = gpa; }
public int getId() { // No setId(): an id is assigned once and never changes afterward return id; }
public String getName() { return name; }
public void setName(String name) { // Setters exist only for the fields update() is allowed to change this.name = name; }
public String getCourse() { return course; }
public void setCourse(String course) { this.course = course; }
public double getGpa() { return gpa; }
public void setGpa(double gpa) { this.gpa = gpa; }
@Override public String toString() { // Overriding toString() means println(student) prints something useful return String.format("ID: %-4d | Name: %-20s | Course: %-15s | GPA: %.2f", id, name, course, gpa); }}Step 2: Build the StudentManager and Add Students
StudentManager is where the collection actually lives. It is declared as List<Student> but instantiated as new ArrayList<>() — programming to the List interface rather than the concrete ArrayList type is a Java convention that keeps the field's declared type flexible if the backing implementation ever needs to change. nextId is a simple auto-incrementing counter, so StudentManager, not the caller, decides every student's id, guaranteeing no two records can ever collide.
import java.util.ArrayList; // Concrete, growable list implementation used to back the students collectionimport java.util.List; // Programming against the List interface, not ArrayList directly, is the Java idiom
// Owns every Student record in memory and is the only class allowed to// create, search, update, or remove them — main() never touches the// ArrayList directly, it only ever calls methods on a StudentManager.class StudentManager { private List<Student> students = new ArrayList<>(); // Grows automatically as students are added; no manual resizing private int nextId = 1; // Next id to hand out; incremented after every successful add
public Student addStudent(String name, String course, double gpa) { Student student = new Student(nextId, name, course, gpa); // The manager assigns the id, not the caller students.add(student); // ArrayList.add() appends in amortized O(1) time nextId++; // Guarantees every future student gets a fresh id return student; // Handy for immediately printing "Added: ..." }}Step 3: List and Search Students
getAll() exposes the collection for listing, and findById() is a plain linear scan using an enhanced for loop — with a class list realistically holding dozens to a few hundred students, a linear search is more than fast enough, and it keeps the code readable. findById() is written once here and reused by both updateStudent() and deleteStudent() in the next two steps, instead of duplicating the same lookup logic three times.
public List<Student> getAll() { return students; // Safe to return the live list here since StudentManager is the only class that mutates it}
public Student findById(int id) { for (Student s : students) { // Enhanced for-loop walks every student in insertion order if (s.getId() == id) { return s; // Found a match: return the actual object, not a defensive copy } } return null; // No match; every caller below must check for null before using the result}Click Run to see what this code prints.
Step 4: Update a Student Record
updateStudent() reuses findById() from Step 3 rather than scanning the list a second time. If no student with that id exists, it returns false immediately and never touches any field — callers use that return value to decide whether to print "updated" or "not found," so StudentManager never needs to print anything itself.
public boolean updateStudent(int id, String newName, String newCourse, double newGpa) { Student student = findById(id); // Reuse the lookup already written in Step 3 instead of duplicating it if (student == null) { return false; // Nothing to update; let the caller report "student not found" } student.setName(newName); // Only StudentManager and code holding a direct Student reference can reach these student.setCourse(newCourse); // setters, which is exactly the encapsulation boundary set up in Step 1 student.setGpa(newGpa); return true;}Step 5: Delete a Student
deleteStudent() uses ArrayList.removeIf(), which takes a lambda predicate and removes every element the predicate matches, returning true only if at least one element was actually removed. Since ids are unique, at most one student is ever removed here, but removeIf() is still the cleanest way to express "delete the student whose id equals this one" without manually managing a loop index while removing from the list being iterated.
public boolean deleteStudent(int id) { return students.removeIf(s -> s.getId() == id); // Lambda predicate; true only if a matching student was removed}Step 6: Build the Menu Loop
main() creates one StudentManager shared by every menu action and loops on a numbered menu until the user chooses to exit. Notice the scanner.nextLine() call right after every scanner.nextInt() or scanner.nextDouble() — those methods consume only the number typed, leaving the trailing newline in the input buffer, so an unconsumed leftover newline would otherwise be read as an empty line the next time nextLine() runs.
import java.util.Scanner;
public class StudentManagementSystem { public static void main(String[] args) { StudentManager manager = new StudentManager(); // One manager instance shared by every menu action below Scanner scanner = new Scanner(System.in); int choice;
do { System.out.println("\n===== STUDENT MANAGEMENT SYSTEM ====="); System.out.println("1. Add Student"); System.out.println("2. List All Students"); System.out.println("3. Search Student by ID"); System.out.println("4. Update Student"); System.out.println("5. Delete Student"); System.out.println("6. Exit"); System.out.print("Enter your choice: "); choice = scanner.nextInt(); scanner.nextLine(); // Discard the leftover newline left behind by nextInt()
if (choice == 1) { System.out.print("Enter name: "); String name = scanner.nextLine(); System.out.print("Enter course: "); String course = scanner.nextLine(); System.out.print("Enter GPA: "); double gpa = scanner.nextDouble(); scanner.nextLine(); Student added = manager.addStudent(name, course, gpa); System.out.println("Added! Assigned ID: " + added.getId()); } else if (choice == 2) { System.out.println("\n--- All Students ---"); for (Student s : manager.getAll()) { // Reuses Student's toString() from Step 1 for each line System.out.println(s); } } else if (choice == 3) { System.out.print("Enter ID to search: "); int id = scanner.nextInt(); scanner.nextLine(); Student found = manager.findById(id); if (found == null) { System.out.println("No student with that ID."); } else { System.out.println(found); } } else if (choice == 4) { System.out.print("Enter ID to update: "); int id = scanner.nextInt(); scanner.nextLine(); if (manager.findById(id) == null) { System.out.println("No student with that ID."); } else { System.out.print("Enter new name: "); String newName = scanner.nextLine(); System.out.print("Enter new course: "); String newCourse = scanner.nextLine(); System.out.print("Enter new GPA: "); double newGpa = scanner.nextDouble(); scanner.nextLine(); manager.updateStudent(id, newName, newCourse, newGpa); System.out.println("Student updated."); } } else if (choice == 5) { System.out.print("Enter ID to delete: "); int id = scanner.nextInt(); scanner.nextLine(); boolean removed = manager.deleteStudent(id); System.out.println(removed ? "Student deleted." : "No student with that ID."); } else if (choice == 6) { System.out.println("Goodbye!"); } else { System.out.println("Invalid choice, try again."); // Catches anything outside 1-6 } } while (choice != 6); // Keep looping until the user explicitly picks Exit
scanner.close(); // Release the Scanner's underlying resources now that the loop has ended }}Complete Code
Here is the full program with every class assembled in the correct order, ready to save as StudentManagementSystem.java and run with javac StudentManagementSystem.java && java StudentManagementSystem.
import java.util.ArrayList;import java.util.List;import java.util.Scanner;
class Student { private int id; private String name; private String course; private double gpa;
public Student(int id, String name, String course, double gpa) { this.id = id; this.name = name; this.course = course; this.gpa = gpa; }
public int getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getCourse() { return course; }
public void setCourse(String course) { this.course = course; }
public double getGpa() { return gpa; }
public void setGpa(double gpa) { this.gpa = gpa; }
@Override public String toString() { return String.format("ID: %-4d | Name: %-20s | Course: %-15s | GPA: %.2f", id, name, course, gpa); }}
class StudentManager { private List<Student> students = new ArrayList<>(); private int nextId = 1;
public Student addStudent(String name, String course, double gpa) { Student student = new Student(nextId, name, course, gpa); students.add(student); nextId++; return student; }
public List<Student> getAll() { return students; }
public Student findById(int id) { for (Student s : students) { if (s.getId() == id) { return s; } } return null; }
public boolean updateStudent(int id, String newName, String newCourse, double newGpa) { Student student = findById(id); if (student == null) { return false; } student.setName(newName); student.setCourse(newCourse); student.setGpa(newGpa); return true; }
public boolean deleteStudent(int id) { return students.removeIf(s -> s.getId() == id); }}
public class StudentManagementSystem { public static void main(String[] args) { StudentManager manager = new StudentManager(); Scanner scanner = new Scanner(System.in); int choice;
do { System.out.println("\n===== STUDENT MANAGEMENT SYSTEM ====="); System.out.println("1. Add Student"); System.out.println("2. List All Students"); System.out.println("3. Search Student by ID"); System.out.println("4. Update Student"); System.out.println("5. Delete Student"); System.out.println("6. Exit"); System.out.print("Enter your choice: "); choice = scanner.nextInt(); scanner.nextLine();
if (choice == 1) { System.out.print("Enter name: "); String name = scanner.nextLine(); System.out.print("Enter course: "); String course = scanner.nextLine(); System.out.print("Enter GPA: "); double gpa = scanner.nextDouble(); scanner.nextLine(); Student added = manager.addStudent(name, course, gpa); System.out.println("Added! Assigned ID: " + added.getId()); } else if (choice == 2) { System.out.println("\n--- All Students ---"); for (Student s : manager.getAll()) { System.out.println(s); } } else if (choice == 3) { System.out.print("Enter ID to search: "); int id = scanner.nextInt(); scanner.nextLine(); Student found = manager.findById(id); if (found == null) { System.out.println("No student with that ID."); } else { System.out.println(found); } } else if (choice == 4) { System.out.print("Enter ID to update: "); int id = scanner.nextInt(); scanner.nextLine(); if (manager.findById(id) == null) { System.out.println("No student with that ID."); } else { System.out.print("Enter new name: "); String newName = scanner.nextLine(); System.out.print("Enter new course: "); String newCourse = scanner.nextLine(); System.out.print("Enter new GPA: "); double newGpa = scanner.nextDouble(); scanner.nextLine(); manager.updateStudent(id, newName, newCourse, newGpa); System.out.println("Student updated."); } } else if (choice == 5) { System.out.print("Enter ID to delete: "); int id = scanner.nextInt(); scanner.nextLine(); boolean removed = manager.deleteStudent(id); System.out.println(removed ? "Student deleted." : "No student with that ID."); } else if (choice == 6) { System.out.println("Goodbye!"); } else { System.out.println("Invalid choice, try again."); } } while (choice != 6);
scanner.close(); }}Sample Run
Click Run to see what this code prints.
Extend This Project
- Add a
findByName(String query)method that returns every student whose name contains the query, case-insensitively. - Switch
StudentManager's backing collection fromArrayList<Student>toHashMap<Integer, Student>keyed by id, turningfindById()from an O(n) scan into an O(1) lookup. - Persist students to a text file with
java.io.FileWriter/BufferedReaderso records survive between program runs. - Add input validation that rejects a GPA outside the 0.0-10.0 range before a student is added or updated.
- Add a
sortByGpa()method usingstudents.sort(Comparator.comparingDouble(Student::getGpa))to list top performers first.
Summary
You built a working student management system where Student protects its own data behind private fields and public accessors, and StudentManager composes many students into one manageable, searchable collection backed by ArrayList. The add/find/update/delete shape you wrote here — one class owning a collection and exposing a small, well-named method per operation — is the same shape you will reuse in almost every data-driven Java console application from here on.