LearnAI ToolsCareerPractice BuildsPlayDevelopmentContact
JavaBeginner~1.5 hours

Student Management System

Build a console app to add, update, search, and delete student records using classes and collections.

ClassesObjectsCollections

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.

What You'll Build
  • A Student class with private id, name, course, and gpa fields plus getters/setters.
  • A StudentManager class that owns an ArrayList<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 on ArrayList.removeIf().
  • A Scanner-based menu loop tying every operation together into one running program.

Prerequisites

  • Classes and objects — defining a class with private fields, a constructor, and public methods.
  • Collections basics — java.util.ArrayList and the List interface, plus the enhanced for loop.
  • Console input — reading lines and numbers with java.util.Scanner.
  • Method basics — parameters, return types, and calling one method from another.
  • The this keyword — 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 collection
import 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
}
Example Usage

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

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 from ArrayList<Student> to HashMap<Integer, Student> keyed by id, turning findById() from an O(n) scan into an O(1) lookup.
  • Persist students to a text file with java.io.FileWriter/BufferedReader so 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 using students.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.