October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk8 min

How to Build a Student Course Registration System to Learn Java OOP

A step-by-step guide to building a small course registration system in Java, using classes, objects, interfaces, and packages to enforce enrollment rules.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A course registration system is a practical first Java project because it forces you to model real things, keep each thing’s data with the behavior that changes it, and enforce rules such as capacity limits and duplicate enrollments. This guide walks through a reference design with three core classes, one interface, and a small entry point. It is a design you can follow and adapt. It is not a report on any particular codebase, so the Java concepts below are tied to Oracle’s official documentation rather than to measured outcomes.

What the system needs to do

Keep the first version small. A workable scope for a learning project is:

  • Register a student with an ID and a name.
  • Create a course with a code, a title, and a maximum capacity.
  • Enroll a registered student in an existing course.
  • Reject enrollment when the course is full, the student is unknown, the course does not exist, or the student is already enrolled.
  • List the courses that are available.

Prerequisites, waitlists, time-conflict checks, drop operations, and persistence to a file or database are real registration features, but each adds a separate design question. Leave them for later versions.

The OOP ideas the project exercises

Oracle’s tutorial lesson Object-Oriented Programming Concepts defines the two foundations in plain terms. A class is “a blueprint or prototype from which objects are created,” and an object is “a software bundle of related state and behavior.” In a registration system, a Student object holds an ID and a name, and it is the class that says what every student object looks like.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Classes and objects

Give each class fields for state that must persist across method calls, and methods for behavior that acts on that state. Keep fields private and expose only what other classes need. This is encapsulation, and it matters here because the capacity rule must not be bypassed by code that edits a course’s roster directly.

Inheritance

Oracle’s tutorial presents inheritance as a way to organize software through superclass and subclass relationships. In Java a class has exactly one direct superclass, so inheritance is a single-chain tool. Use it only when subclasses genuinely share behavior. If you have only Student and nothing else that shares its structure, do not add a superclass just to demonstrate the feature. A defensible case appears only when you also model something like an instructor, and both share a Person base with an ID and a name.

Interfaces

The tutorial describes an interface as a contract that a class promises to implement. Unlike a class, an interface can be implemented by many unrelated classes, and a class can implement several interfaces. The Java Language Specification states that classes support single inheritance while interfaces may extend multiple interfaces, so do not describe a Java class as having multiple inheritance. This project uses an interface for storage, described in the build steps below.

Packages

A package is a namespace for related classes and interfaces. Splitting the code into model, service, and app packages makes the dependency direction visible: model classes know nothing about the service layer, and the entry point wires everything together.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Map the domain to classes

Before writing code, decide which class owns each piece of state and each rule. The reference design uses this split:

Class or interface Package State it holds Behavior it owns
Student model ID, name Identity and display data only
Course model Code, title, capacity, roster of enrolled student IDs Checks capacity and rejects duplicate enrollment
CourseRepository (interface) service None (a contract) Find, list, and save courses
InMemoryCourseRepository service Map of course code to Course Implements the contract using memory only
Registrar service Map of student ID to Student, plus a reference to the repository Coordinates enrollment: checks that student and course exist, then delegates the rule to Course

The key decision is that Course enforces its own capacity. If the Registrar checked the roster directly, every future caller would have to repeat that check correctly. Keeping the rule with the data it protects is the central object-oriented habit this project teaches.

Build the project step by step

  1. Install a current JDK. The examples assume Java SE 21 or a later long-term-support release. Check the installed version with java -version and javac -version, and confirm they match.

  2. Create the folder layout. The source path must mirror the package name:

    Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
    registration/
      src/
        com/example/registration/model/
        com/example/registration/service/
        com/example/registration/app/
  3. Write Student. Save it as src/com/example/registration/model/Student.java:

    package com.example.registration.model;
    
    public class Student {
        private final String id;
        private final String name;
    
        public Student(String id, String name) {
            this.id = id;
            this.name = name;
        }
    
        public String getId() { return id; }
        public String getName() { return name; }
    }
  4. Write Course. Save it in the same model folder. This class owns the capacity rule:

    package com.example.registration.model;
    
    import java.util.LinkedHashSet;
    import java.util.Set;
    
    public class Course {
        private final String code;
        private final String title;
        private final int capacity;
        private final Set<String> enrolledStudentIds = new LinkedHashSet<>();
    
        public Course(String code, String title, int capacity) {
            if (capacity < 1) {
                throw new IllegalArgumentException("Capacity must be at least 1");
            }
            this.code = code;
            this.title = title;
            this.capacity = capacity;
        }
    
        public String getCode() { return code; }
        public String getTitle() { return title; }
    
        public boolean isFull() {
            return enrolledStudentIds.size() >= capacity;
        }
    
        public void addStudent(String studentId) {
            if (isFull()) {
                throw new IllegalStateException("Course " + code + " is full");
            }
            if (!enrolledStudentIds.add(studentId)) {
                throw new IllegalStateException("Student " + studentId + " is already enrolled in " + code);
            }
        }
    }

    LinkedHashSet keeps duplicate checks fast while preserving enrollment order, which makes printed rosters predictable. The Java SE 21 API documents the Collections Framework that these types belong to.

  5. Define the storage contract. Save this interface in service as CourseRepository.java:

    Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
    package com.example.registration.service;
    
    import com.example.registration.model.Course;
    import java.util.List;
    import java.util.Optional;
    
    public interface CourseRepository {
        Optional<Course> findByCode(String code);
        List<Course> findAll();
        void save(Course course);
    }

    Any class that implements this interface must provide all three methods. The Registrar depends only on the contract, so a file-based or database version can replace the memory version without changing enrollment logic.

  6. Implement the interface in memory. Save InMemoryCourseRepository.java in service, using a HashMap keyed by course code. Its save method puts the course into the map, and findAll returns a new list of its values so callers cannot modify the map.

  7. Write Registrar. Save it in service:

    package com.example.registration.service;
    
    import com.example.registration.model.Course;
    import com.example.registration.model.Student;
    import java.util.HashMap;
    import java.util.Map;
    
    public class Registrar {
        private final CourseRepository courses;
        private final Map<String, Student> students = new HashMap<>();
    
        public Registrar(CourseRepository courses) {
            this.courses = courses;
        }
    
        public void addStudent(Student student) {
            students.put(student.getId(), student);
        }
    
        public void enroll(String studentId, String courseCode) {
            if (!students.containsKey(studentId)) {
                throw new IllegalArgumentException("Unknown student " + studentId);
            }
            Course course = courses.findByCode(courseCode)
                    .orElseThrow(() -> new IllegalArgumentException("Unknown course " + courseCode));
            course.addStudent(studentId);
            courses.save(course);
        }
    }

    The final save call is redundant with the memory store, where the same object is already in the map. It is included because a database-backed repository would need it, and calling it keeps the service code correct for both.

  8. Write a short entry point. Save Main.java in app. Create the repository, register one or two courses and students, enroll a student, and print the result. Then deliberately enroll a second student into a course with capacity 1 and confirm that the exception message appears.

    Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  9. Compile from the project root. On macOS or Linux:

    javac -d out $(find src -name "*.java")
    java -cp out com.example.registration.app.Main

    On Windows Command Prompt, run dir /s /b src*.java > sources.txt, then javac -d out @sources.txt, then java -cp out com.example.registration.app.Main.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Check the rules before you extend the project

Test the rules with inputs you choose in advance. The expected behavior is:

  • Enrolling a registered student in a course with free seats succeeds, and the course reports the same number of enrolled students you expected.
  • Enrolling the same student twice in the same course throws IllegalStateException with the duplicate message.
  • Enrolling a fourth student in a course with capacity 3 throws IllegalStateException with the full message.
  • Enrolling an ID that was never registered throws IllegalArgumentException before the course is touched.

Write these checks as a main method or, once you are comfortable, as JUnit tests. Each check corresponds to one rule in the design, which makes failures easy to locate.

Common problems and fixes

Symptom Likely cause Fix
class Course is public, should be declared in a file named Course.java The file name does not match the public class name Rename the file so it matches the class exactly, including capitalization
package com.example.registration.model does not exist The source folder layout does not match the package declaration Move the file so its folder path matches its package line
Error: Could not find or load main class The run command omits -cp out or uses the wrong fully qualified name Run java -cp out com.example.registration.app.Main from the project root
Registered courses disappear after the program exits The in-memory repository keeps data only while the program runs Expected behavior. Persistence requires a second implementation of CourseRepository
Enrollment fails with a full-course message on a course that looks empty The same course object was not reused, or capacity was set lower than expected Confirm the course stored in the repository is the instance you modified, and print its capacity when it is created

Which Java learning material to follow

Oracle’s older Java tutorial, including the OOP lessons used as a reference here, uses examples written in the JDK 8 era. The language concepts are still accurate, but its code style and some library usage are dated. Oracle directs readers to Dev.java for updated material. Dev.java’s OOP learning section covers classes and packages, interfaces, records, and inheritance, which together match the topics in this project. Records are a natural next step: a read-only Enrollment record holding a student ID and course code could replace the roster set when you add enrollment history.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Where the reference design stops

This design does not handle concurrent registrations, a graphical or web interface, authentication, or real institutional rules such as prerequisites and time conflicts. Adding any of them changes the design, not just the code. Capacity and duplicate checks are the only validation rules implemented here, and they are enforced in memory during one run of the program.

Sources

  • Oracle, Lesson: Object-Oriented Programming Concepts, The Java Tutorials (quoted definitions of class and object, and descriptions of inheritance, interfaces, and packages).
  • Oracle, The Java Language Specification, Chapter 1 (language description and inheritance rules).
  • Oracle, Java SE 21 API documentation, Java Collections Framework.
  • Dev.java, OOP learning section (classes and packages, interfaces, records, inheritance).

The project code above is an illustrative design written for this guide. It has not been measured against any particular course or enrollment dataset.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.