Lesson 25 of 25
Student Course Management System
Combine Java, Swing, JDBC, validation, and SQLite CRUD operations into a complete capstone desktop application.
Capstone objectives
- Combine the OOP, Swing, JDBC, validation, and collection concepts from earlier lessons.
- Create a layered desktop project instead of placing all code in one class.
- Load database records into a
JTable. - Insert, update, search, and delete student records from the GUI.
- Keep database logic out of Swing event handlers.
- Provide user-friendly validation and confirmation messages.
1. Capstone architecture
The application uses four responsibilities. The Student class represents data and business rules. DatabaseHelper opens database connections. StudentRepository performs SQL operations. StudentManagementFrame handles user interaction.
student-course-management/
├── DatabaseHelper.java
├── DatabaseSetup.java
├── Student.java
├── StudentRepository.java
└── StudentManagementFrame.java
2. Database schema
CREATE TABLE IF NOT EXISTS students (
student_id TEXT PRIMARY KEY,
name TEXT NOT NULL,
course TEXT NOT NULL,
mark INTEGER NOT NULL
CHECK(mark BETWEEN 0 AND 100)
);
3. Reuse the model and repository
Use the Student, DatabaseHelper, DatabaseSetup, and StudentRepository classes developed in Lesson 24. This is intentional: production applications reuse working components rather than rewriting database code inside every form.
4. Build the Swing form
The interface needs text fields for student ID, name, course, and mark; buttons for Save, Update, Delete, Search, and Clear; plus a table for existing records.
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
public class StudentManagementFrame extends JFrame {
private final StudentRepository repository =
new StudentRepository();
private final JTextField txtStudentId =
new JTextField();
private final JTextField txtName =
new JTextField();
private final JTextField txtCourse =
new JTextField();
private final JTextField txtMark =
new JTextField();
private final DefaultTableModel tableModel =
new DefaultTableModel(
new String[]{
"Student ID",
"Name",
"Course",
"Mark",
"Result"
},
0
) {
@Override
public boolean isCellEditable(
int row, int column) {
return false;
}
};
private final JTable table =
new JTable(tableModel);
public StudentManagementFrame() {
setTitle("Student Course Management System");
setDefaultCloseOperation(EXIT_ON_CLOSE);
buildInterface();
DatabaseSetup.initialize();
loadStudents();
setSize(850, 500);
setLocationRelativeTo(null);
}
5. Create the form layout and buttons
private void buildInterface() {
JPanel form =
new JPanel(new GridLayout(4, 2, 8, 8));
form.add(new JLabel("Student ID:"));
form.add(txtStudentId);
form.add(new JLabel("Name:"));
form.add(txtName);
form.add(new JLabel("Course:"));
form.add(txtCourse);
form.add(new JLabel("Mark:"));
form.add(txtMark);
JButton btnSave = new JButton("Save");
JButton btnUpdate = new JButton("Update");
JButton btnDelete = new JButton("Delete");
JButton btnSearch = new JButton("Search");
JButton btnClear = new JButton("Clear");
JPanel buttons = new JPanel(new FlowLayout());
buttons.add(btnSave);
buttons.add(btnUpdate);
buttons.add(btnDelete);
buttons.add(btnSearch);
buttons.add(btnClear);
JPanel top = new JPanel(new BorderLayout(8, 8));
top.add(form, BorderLayout.CENTER);
top.add(buttons, BorderLayout.SOUTH);
add(top, BorderLayout.NORTH);
add(
new JScrollPane(table),
BorderLayout.CENTER
);
btnSave.addActionListener(
event -> saveStudent()
);
btnUpdate.addActionListener(
event -> updateStudent()
);
btnDelete.addActionListener(
event -> deleteStudent()
);
btnSearch.addActionListener(
event -> searchStudent()
);
btnClear.addActionListener(
event -> clearFields()
);
table.getSelectionModel()
.addListSelectionListener(event -> {
if (!event.getValueIsAdjusting()) {
fillFieldsFromSelectedRow();
}
});
}
6. Convert form input into a Student object
Centralizing validation avoids repeating the same checks in every button handler.
private Student readStudentFromForm() {
String id =
txtStudentId.getText().trim();
String name =
txtName.getText().trim();
String course =
txtCourse.getText().trim();
if (id.isBlank()
|| name.isBlank()
|| course.isBlank()) {
throw new IllegalArgumentException(
"Student ID, name, and course are required."
);
}
int mark;
try {
mark = Integer.parseInt(
txtMark.getText().trim()
);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"Mark must be a whole number."
);
}
return new Student(
id, name, course, mark
);
}
7. Save a student
private void saveStudent() {
try {
Student student =
readStudentFromForm();
if (repository.add(student)) {
JOptionPane.showMessageDialog(
this,
"Student saved successfully."
);
loadStudents();
clearFields();
}
} catch (RuntimeException e) {
showError(e);
}
}
8. Update a student
private void updateStudent() {
try {
Student student =
readStudentFromForm();
if (repository.update(student)) {
JOptionPane.showMessageDialog(
this,
"Student updated successfully."
);
loadStudents();
clearFields();
} else {
JOptionPane.showMessageDialog(
this,
"Student ID was not found."
);
}
} catch (RuntimeException e) {
showError(e);
}
}
9. Search by student ID
private void searchStudent() {
String id =
txtStudentId.getText().trim();
if (id.isBlank()) {
JOptionPane.showMessageDialog(
this,
"Enter a Student ID to search."
);
return;
}
repository.findById(id)
.ifPresentOrElse(
student -> {
txtName.setText(
student.getName()
);
txtCourse.setText(
student.getCourse()
);
txtMark.setText(
String.valueOf(
student.getMark()
)
);
},
() ->
JOptionPane.showMessageDialog(
this,
"Student not found."
)
);
}
10. Delete with confirmation
private void deleteStudent() {
String id =
txtStudentId.getText().trim();
if (id.isBlank()) {
JOptionPane.showMessageDialog(
this,
"Select or enter a Student ID."
);
return;
}
int choice = JOptionPane.showConfirmDialog(
this,
"Delete student " + id + "?",
"Confirm Delete",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE
);
if (choice != JOptionPane.YES_OPTION) {
return;
}
try {
if (repository.delete(id)) {
JOptionPane.showMessageDialog(
this,
"Student deleted."
);
loadStudents();
clearFields();
} else {
JOptionPane.showMessageDialog(
this,
"Student ID was not found."
);
}
} catch (RuntimeException e) {
showError(e);
}
}
11. Reload the JTable
private void loadStudents() {
tableModel.setRowCount(0);
for (Student student :
repository.findAll()) {
tableModel.addRow(
new Object[]{
student.getStudentId(),
student.getName(),
student.getCourse(),
student.getMark(),
student.getResult()
}
);
}
}
12. Select a row and edit it
private void fillFieldsFromSelectedRow() {
int row = table.getSelectedRow();
if (row < 0) {
return;
}
txtStudentId.setText(
tableModel.getValueAt(row, 0).toString()
);
txtName.setText(
tableModel.getValueAt(row, 1).toString()
);
txtCourse.setText(
tableModel.getValueAt(row, 2).toString()
);
txtMark.setText(
tableModel.getValueAt(row, 3).toString()
);
}
private void clearFields() {
txtStudentId.setText("");
txtName.setText("");
txtCourse.setText("");
txtMark.setText("");
table.clearSelection();
txtStudentId.requestFocus();
}
private void showError(RuntimeException e) {
String message =
e.getCause() != null
? e.getCause().getMessage()
: e.getMessage();
JOptionPane.showMessageDialog(
this,
message,
"Error",
JOptionPane.ERROR_MESSAGE
);
}
13. Start the application
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new StudentManagementFrame()
.setVisible(true);
});
}
}
14. What this capstone demonstrates
| Concept | Where it appears |
|---|---|
| OOP | Student model and encapsulated validation |
| Interfaces between layers | GUI calls repository methods instead of embedding SQL |
| Swing | Forms, buttons, event handlers, JTable, dialogs |
| JDBC | Prepared statements and result mapping |
| SQLite | Persistent local database |
| Validation | Required fields, numeric mark, mark range |
| CRUD | Save, load/search, update, delete |
Production improvements to try next
- Replace the free-text course field with a
JComboBoxpopulated from a separatecoursestable. - Add a second table for courses and a foreign key from students to courses.
- Add search by name or course.
- Move database work off the Swing Event Dispatch Thread using
SwingWorkerfor larger datasets. - Add unit tests for
Studentvalidation and repository integration tests against a temporary database. - Add export to CSV and a printable student report.
Common mistakes
| Problem | Why it happens | Fix |
|---|---|---|
| All SQL is placed inside button handlers | The GUI becomes difficult to maintain and test. | Keep SQL inside StudentRepository. |
| The JTable is not refreshed after a change | The database changed but the displayed model did not. | Call loadStudents() after successful save/update/delete. |
| Delete happens immediately | Users can remove data accidentally. | Ask for confirmation before deleting. |
| Database operations freeze a large GUI | Slow I/O is executing on the EDT. | Use a background worker for long-running database work. |
Capstone challenge
Extend the system into a true Student Course Management System by adding a courses table with course_id, course_name, and lecturer. Create a Course model and repository, load courses into a combo box, and save the selected course_id with each student. This introduces a relational design rather than storing course names repeatedly as free text.
Course summary
You have now progressed from Java fundamentals through object-oriented programming, collections, lambdas, date/time, Swing, JDBC, SQLite, CRUD, and a complete desktop database application. The most important next step is to keep building: add features, refactor duplicated code, test each layer, and place the finished source code under version control.