Java Notes
Build a complete Student Management System with Java and MySQL - CRUD operations, search functionality, report generation, and layered architecture.
Project Overview
The Student Management System is a complete Java Spring Boot application that manages student records, course enrollments, grade tracking, attendance management, and academic report generation. It implements full CRUD operations with a clean layered architecture (Controller → Service → Repository) and provides both REST API access and an optional Thymeleaf-based web UI.
This project is ideal for:
- Java beginners building their first Spring Boot project
- Students learning CRUD operations with JPA/Hibernate
- Developers preparing for Java developer interviews with hands-on code
- Anyone wanting to understand the complete request-response lifecycle in Spring Boot
The system manages multiple entities (Students, Courses, Enrollments, Grades, Attendance) with proper relationships, implements comprehensive search and filtering, and generates PDF report cards with GPA calculation.
Features
- Student Registration — Register students with personal info, contact details, and photo
- Course Management — Create and manage courses with credits, department, and prerequisites
- Course Enrollment — Enroll students in courses with semester tracking
- Grade Management — Record grades for assignments, midterms, and finals
- GPA Calculation — Automatic CGPA/SGPA calculation based on grade points and credits
- Attendance Tracking — Daily attendance marking with percentage calculation
- Search & Filter — Search students by name, ID, department, or enrollment year
- Pagination & Sorting — Efficient listing with configurable page size and sort options
- Report Card Generation — PDF report cards with grades, GPA, and attendance
- Batch Operations — Bulk student import from CSV/Excel
- Dashboard — Overview with total students, department distribution, top performers
- Semester Management — Track academic semesters and year progression
- Fee Management — Track fee payment status per semester
- Parent/Guardian Info — Store emergency contact and guardian details
- Document Upload — Upload student photo, ID proof, and certificates
- Email Notifications — Registration confirmation, grade published alerts
- Export Data — Export student list as Excel or CSV
- Validation — Comprehensive input validation with meaningful error messages
- Audit Trail — Track record modifications with timestamps
- Role-Based Views — Admin (full access), Faculty (grades/attendance), Student (view own)
Project Structure
Database Schema
students Table
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | Unique ID |
| roll_number | VARCHAR(15) | UNIQUE, NOT NULL | Roll number (CS2024001) |
| first_name | VARCHAR(50) | NOT NULL | First name |
| last_name | VARCHAR(50) | NOT NULL | Last name |
| VARCHAR(100) | UNIQUE, NOT NULL | Email address | |
| phone | VARCHAR(15) | Contact number | |
| gender | ENUM | NOT NULL | MALE, FEMALE, OTHER |
| date_of_birth | DATE | NOT NULL | Birth date |
| admission_date | DATE | NOT NULL | Admission date |
| department | ENUM | NOT NULL | CS, IT, ECE, ME, CE, EE |
| current_semester | INT | DEFAULT 1 | Current semester (1-8) |
| cgpa | DECIMAL(3,2) | DEFAULT 0.00 | Cumulative GPA |
| status | ENUM | DEFAULT 'ACTIVE' | ACTIVE, GRADUATED, DROPPED, SUSPENDED |
| address | VARCHAR(255) | Residential address | |
| guardian_name | VARCHAR(100) | Parent/Guardian name | |
| guardian_phone | VARCHAR(15) | Guardian contact | |
| photo_url | VARCHAR(255) | Profile photo | |
| password | VARCHAR(255) | NOT NULL | Login password (hashed) |
| created_at | TIMESTAMP | NOT NULL | Registration time |
| updated_at | TIMESTAMP | NOT NULL | Last update time |
courses Table
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | Unique ID |
| course_code | VARCHAR(10) | UNIQUE, NOT NULL | Code (CS301) |
| name | VARCHAR(100) | NOT NULL | Course name |
| description | TEXT | Course description | |
| credits | INT | NOT NULL | Credit hours (1-5) |
| department | ENUM | NOT NULL | Offering department |
| semester | INT | NOT NULL | Which semester |
| max_capacity | INT | DEFAULT 60 | Max students |
| faculty_name | VARCHAR(100) | Teaching faculty | |
| is_active | BOOLEAN | DEFAULT TRUE | Currently offered |
enrollments Table
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | Unique ID |
| student_id | BIGINT | FOREIGN KEY | Student reference |
| course_id | BIGINT | FOREIGN KEY | Course reference |
| semester_id | BIGINT | FOREIGN KEY | Semester reference |
| enrolled_date | DATE | NOT NULL | Enrollment date |
| status | ENUM | DEFAULT 'ENROLLED' | ENROLLED, COMPLETED, DROPPED, FAILED |
grades Table
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | Unique ID |
| enrollment_id | BIGINT | FOREIGN KEY | Enrollment reference |
| assignment_marks | DECIMAL(5,2) | Assignment (out of 20) | |
| midterm_marks | DECIMAL(5,2) | Midterm (out of 30) | |
| final_marks | DECIMAL(5,2) | Final exam (out of 50) | |
| total_marks | DECIMAL(5,2) | Total (out of 100) | |
| grade_letter | ENUM | A+, A, B+, B, C+, C, D, F | |
| grade_point | DECIMAL(3,1) | Grade point (0.0 - 10.0) | |
| published | BOOLEAN | DEFAULT FALSE | Visible to student |
| published_date | DATE | When made visible |
attendance Table
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | Unique ID |
| student_id | BIGINT | FOREIGN KEY | Student reference |
| course_id | BIGINT | FOREIGN KEY | Course reference |
| date | DATE | NOT NULL | Attendance date |
| status | ENUM | NOT NULL | PRESENT, ABSENT, LATE, EXCUSED |
| remarks | VARCHAR(255) | Notes |
Entity Relationships
Key Code Snippets
Student Entity
package com.wohotech.sms.entity;
import com.wohotech.sms.enums.*;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "students")
@Getter @Setter
@NoArgsConstructor @AllArgsConstructor
@Builder
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "roll_number", unique = true, nullable = false, length = 15)
private String rollNumber;
@Column(name = "first_name", nullable = false, length = 50)
private String firstName;
@Column(name = "last_name", nullable = false, length = 50)
private String lastName;
@Column(unique = true, nullable = false, length = 100)
private String email;
@Column(length = 15)
private String phone;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Gender gender;
@Column(name = "date_of_birth", nullable = false)
private LocalDate dateOfBirth;
@Column(name = "admission_date", nullable = false)
private LocalDate admissionDate;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Department department;
@Column(name = "current_semester")
@Builder.Default
private Integer currentSemester = 1;
@Column(precision = 3, scale = 2)
@Builder.Default
private BigDecimal cgpa = BigDecimal.ZERO;
@Enumerated(EnumType.STRING)
@Builder.Default
private StudentStatus status = StudentStatus.ACTIVE;
private String address;
@Column(name = "guardian_name", length = 100)
private String guardianName;
@Column(name = "guardian_phone", length = 15)
private String guardianPhone;
@Column(name = "photo_url")
private String photoUrl;
@Column(nullable = false)
private String password;
@OneToMany(mappedBy = "student", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<Enrollment> enrollments = new ArrayList<>();
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// Helper methods
public String getFullName() {
return firstName + " " + lastName;
}
public int getAcademicYear() {
return (currentSemester + 1) / 2; // Sem 1-2 = Year 1, Sem 3-4 = Year 2, etc.
}
}Student Service Implementation
GPA Calculator Service
package com.wohotech.sms.service;
import com.wohotech.sms.entity.Enrollment;
import com.wohotech.sms.entity.Grade;
import com.wohotech.sms.repository.EnrollmentRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
@Service
@RequiredArgsConstructor
public class GpaCalculatorService {
private final EnrollmentRepository enrollmentRepository;
/**
* CGPA = Σ(Grade Point × Credits) / Σ(Credits)
*
* Grade Scale (10-point):
* A+ (90-100) = 10.0, A (80-89) = 9.0, B+ (70-79) = 8.0
* B (60-69) = 7.0, C+ (50-59) = 6.0, C (45-49) = 5.0
* D (40-44) = 4.0, F (below 40) = 0.0
*
* Example: Student with 3 courses
* - Data Structures (4 credits) - Grade A (9.0): 4 × 9.0 = 36
* - OS (3 credits) - Grade B+ (8.0): 3 × 8.0 = 24
* - Math (3 credits) - Grade A+ (10.0): 3 × 10.0 = 30
* CGPA = (36 + 24 + 30) / (4 + 3 + 3) = 90 / 10 = 9.0
*/
public BigDecimal calculateCGPA(Long studentId) {
List<Enrollment> completedEnrollments = enrollmentRepository
.findCompletedEnrollmentsWithGrades(studentId);
if (completedEnrollments.isEmpty()) {
return BigDecimal.ZERO;
}
BigDecimal totalWeightedPoints = BigDecimal.ZERO;
int totalCredits = 0;
for (Enrollment enrollment : completedEnrollments) {
Grade grade = enrollment.getGrade();
if (grade != null && grade.getGradePoint() != null) {
int credits = enrollment.getCourse().getCredits();
BigDecimal weightedPoint = grade.getGradePoint()
.multiply(BigDecimal.valueOf(credits));
totalWeightedPoints = totalWeightedPoints.add(weightedPoint);
totalCredits += credits;
}
}
if (totalCredits == 0) return BigDecimal.ZERO;
return totalWeightedPoints.divide(
BigDecimal.valueOf(totalCredits), 2, RoundingMode.HALF_UP);
}
/**
* Convert percentage marks to grade letter and grade point.
*/
public GradeResult convertMarksToGrade(BigDecimal totalMarks) {
double marks = totalMarks.doubleValue();
if (marks >= 90) return new GradeResult("A+", new BigDecimal("10.0"));
if (marks >= 80) return new GradeResult("A", new BigDecimal("9.0"));
if (marks >= 70) return new GradeResult("B+", new BigDecimal("8.0"));
if (marks >= 60) return new GradeResult("B", new BigDecimal("7.0"));
if (marks >= 50) return new GradeResult("C+", new BigDecimal("6.0"));
if (marks >= 45) return new GradeResult("C", new BigDecimal("5.0"));
if (marks >= 40) return new GradeResult("D", new BigDecimal("4.0"));
return new GradeResult("F", BigDecimal.ZERO);
}
public record GradeResult(String letter, BigDecimal point) {}
}Student Controller
package com.wohotech.sms.controller;
import com.wohotech.sms.dto.DashboardDTO;
import com.wohotech.sms.dto.StudentDTO;
import com.wohotech.sms.service.StudentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/students")
@RequiredArgsConstructor
@Tag(name = "Student Management", description = "Student CRUD and academic operations")
public class StudentController {
private final StudentService studentService;
@PostMapping
@Operation(summary = "Register a new student")
public ResponseEntity<StudentDTO> createStudent(
@Valid @RequestBody StudentDTO request) {
return new ResponseEntity<>(studentService.createStudent(request), HttpStatus.CREATED);
}
@GetMapping
@Operation(summary = "Get all students with pagination")
public ResponseEntity<Page<StudentDTO>> getAllStudents(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "rollNumber") String sortBy,
@RequestParam(defaultValue = "asc") String direction) {
return ResponseEntity.ok(studentService.getAllStudents(page, size, sortBy, direction));
}
@GetMapping("/{id}")
@Operation(summary = "Get student by ID")
public ResponseEntity<StudentDTO> getStudentById(@PathVariable Long id) {
return ResponseEntity.ok(studentService.getStudentById(id));
}
@GetMapping("/roll/{rollNumber}")
@Operation(summary = "Get student by roll number")
public ResponseEntity<StudentDTO> getByRollNumber(@PathVariable String rollNumber) {
return ResponseEntity.ok(studentService.getStudentByRollNumber(rollNumber));
}
@PutMapping("/{id}")
@Operation(summary = "Update student details")
public ResponseEntity<StudentDTO> updateStudent(
@PathVariable Long id,
@Valid @RequestBody StudentDTO request) {
return ResponseEntity.ok(studentService.updateStudent(id, request));
}
@DeleteMapping("/{id}")
@Operation(summary = "Delete (deactivate) student")
public ResponseEntity<String> deleteStudent(@PathVariable Long id) {
studentService.deleteStudent(id);
return ResponseEntity.ok("Student deleted successfully");
}
@GetMapping("/search")
@Operation(summary = "Search students by keyword")
public ResponseEntity<Page<StudentDTO>> searchStudents(
@RequestParam String q,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return ResponseEntity.ok(studentService.searchStudents(q, page, size));
}
@GetMapping("/department/{department}")
@Operation(summary = "Get students by department")
public ResponseEntity<Page<StudentDTO>> getByDepartment(
@PathVariable String department,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
return ResponseEntity.ok(studentService.getByDepartment(department, page, size));
}
@GetMapping("/dashboard")
@Operation(summary = "Dashboard statistics")
public ResponseEntity<DashboardDTO> getDashboard() {
return ResponseEntity.ok(studentService.getDashboard());
}
}Student Repository
package com.wohotech.sms.repository;
import com.wohotech.sms.entity.Student;
import com.wohotech.sms.enums.Department;
import com.wohotech.sms.enums.StudentStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
Optional<Student> findByRollNumber(String rollNumber);
Optional<Student> findByEmail(String email);
boolean existsByEmail(String email);
Page<Student> findByStatus(StudentStatus status, Pageable pageable);
Page<Student> findByDepartmentAndStatus(Department department,
StudentStatus status, Pageable pageable);
@Query("SELECT s FROM Student s WHERE s.status = 'ACTIVE' AND (" +
"LOWER(s.firstName) LIKE LOWER(CONCAT('%', :keyword, '%')) OR " +
"LOWER(s.lastName) LIKE LOWER(CONCAT('%', :keyword, '%')) OR " +
"LOWER(s.email) LIKE LOWER(CONCAT('%', :keyword, '%')) OR " +
"s.rollNumber LIKE CONCAT('%', :keyword, '%'))")
Page<Student> searchStudents(@Param("keyword") String keyword, Pageable pageable);
List<Student> findTop10ByStatusOrderByCgpaDesc(StudentStatus status);
long countByStatus(StudentStatus status);
@Query("SELECT s.department, COUNT(s) FROM Student s WHERE s.status = 'ACTIVE' " +
"GROUP BY s.department")
List<Object[]> getCountByDepartment();
@Query("SELECT s FROM Student s WHERE s.status = 'ACTIVE' AND s.currentSemester = :semester")
List<Student> findBySemester(@Param("semester") int semester);
@Query("SELECT AVG(s.cgpa) FROM Student s WHERE s.department = :dept AND s.status = 'ACTIVE'")
Double getAverageCgpaByDepartment(@Param("dept") Department department);
}API Endpoints
| Method | URL | Description | Auth |
|---|---|---|---|
| POST | /api/v1/students | Register student | Admin |
| GET | /api/v1/students | List all (paginated) | Any |
| GET | /api/v1/students/{id} | Get by ID | Any |
| GET | /api/v1/students/roll/{rollNumber} | Get by roll number | Any |
| PUT | /api/v1/students/{id} | Update student | Admin |
| DELETE | /api/v1/students/{id} | Soft delete | Admin |
| GET | /api/v1/students/search?q= | Search students | Any |
| GET | /api/v1/students/department/{dept} | By department | Any |
| GET | /api/v1/students/dashboard | Dashboard stats | Admin |
| POST | /api/v1/courses | Create course | Admin |
| GET | /api/v1/courses | List courses | Any |
| POST | /api/v1/enrollments | Enroll student | Admin |
| GET | /api/v1/enrollments/student/{id} | Student courses | Student |
| POST | /api/v1/grades | Record grade | Faculty |
| GET | /api/v1/grades/student/{id}/semester/{sem} | Semester grades | Student |
| POST | /api/v1/attendance/mark | Mark attendance | Faculty |
| GET | /api/v1/attendance/student/{id}/course/{courseId} | Attendance % | Student |
| GET | /api/v1/reports/student/{id}/report-card | PDF report card | Any |
| GET | /api/v1/reports/students/export | Excel export | Admin |
How to Run
Setup Steps
Step 1: Create MySQL database
CREATE DATABASE student_db;Step 2: Configure application.yml
Step 3: Build and run
mvn clean install
mvn spring-boot:runStep 4: Access
- REST API:
http://localhost:8080/api/v1 - Swagger UI:
http://localhost:8080/swagger-ui.html - Web UI:
http://localhost:8080/students(Thymeleaf)
Screenshots Description
- Student List — Table with roll number, name, department, semester, CGPA with pagination
- Registration Form — Student creation form with validation error messages
- Student Profile — Complete student details with enrolled courses and attendance
- Grade Sheet — Semester-wise grades table with GPA calculation
- Report Card PDF — Generated PDF with student info, grades, and CGPA
- Dashboard — Pie chart (department distribution), bar chart (CGPA ranges), stats cards
- Attendance Report — Calendar view showing present/absent days with percentage
Future Enhancements
- Online Exam Module — MCQ-based exams with auto-grading
- Timetable Management — Class schedule generation with conflict detection
- Placement Portal — Company registrations, student applications, interview scheduling
- Hostel Management — Room allocation and mess management
- Library Integration — Connect with library management for book borrowing
- Parent Portal — Guardian login to view child's progress
- Mobile App — Student app for attendance, grades, and notifications
- Analytics — Predictive analytics for at-risk students (low attendance/grades)
- Alumni Network — Graduate tracking and networking
- Multi-institution — SaaS model supporting multiple colleges
Interview Questions
Q1: How does the GPA calculation work in this system?
Answer: CGPA uses the formula: Σ(Grade Point × Credits) / Σ(Credits). Each course has a credit value (1-5). When grades are entered, the system converts percentage marks to grade points (A+=10, A=9, B+=8, etc.) and calculates the weighted average. SGPA is per-semester, CGPA is cumulative across all semesters. The GpaCalculatorService fetches all completed enrollments with grades, multiplies each grade point by course credits, sums them, and divides by total credits.
Q2: How do you handle the relationship between Student, Course, and Grade?
Answer: It's a many-to-many relationship between Student and Course, resolved via the Enrollment junction entity. Each enrollment has exactly one Grade (OneToOne). This design allows: tracking enrollment status (enrolled/completed/dropped), storing semester context, and linking grades to specific student-course-semester combinations. We don't put grades directly on the student or course table to maintain normalization.
Q3: Why use pagination instead of returning all records?
Answer: With thousands of students, returning all records in one response causes: (1) High memory usage on the server (loading all entities from DB), (2) Large JSON response increasing network transfer time, (3) Client-side rendering performance issues. Pagination with PageRequest.of(page, size) generates SQL LIMIT/OFFSET, loading only the requested page. The response includes metadata (totalElements, totalPages, hasNext) for the client to build navigation.
Q4: How do you generate the roll number?
Answer: The RollNumberGenerator creates a unique ID using: Department code (2 chars) + Admission Year (4 digits) + Sequential number (3 digits). Example: CS2024001. It queries the max existing roll number for that department and year, extracts the sequence, increments it, and formats with leading zeros. Thread safety is ensured using @Synchronized or database sequence. The format makes it easy to identify a student's department and admission year.
Q5: How would you implement bulk student import from Excel?
Answer: (1) Controller accepts MultipartFile, (2) Use Apache POI to read each row, (3) Map columns to StudentDTO, validate each record (email format, required fields, no duplicates), (4) Collect valid records and errors separately, (5) Save valid students in batch using saveAll() with hibernate.jdbc.batch_size=50, (6) Return response: {success: 95, failed: 5, errors: [{row: 12, message: "Invalid email"}]}. We process in a single transaction with rollback only for critical errors.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Student Management System — Java Programming.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, projects, student, management
Related Java Master Course Topics