LearnAI ToolsCareerPractice BuildsPlayContact
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.