An automated constraint-satisfaction engine for generating institution-wide academic timetables — managing faculty loads, room capacities, section conflicts, and semester parity in a single, transactional pass.
See the scheduling engine generate a timetable automatically.
- System Overview
- High-Level Architecture
- Domain Model
- Scheduling Engine Design
- API Design
- Database Schema
- Security Model
- Tech Stack
- Local Development Setup
- Docker Deployment
- Project Structure
This system automates the assignment of CourseOfferings (a course assigned to a section and faculty) to TimeSlots and Rooms, while respecting a set of hard constraints:
| Constraint | Description |
|---|---|
| No Room Double-Booking | A room cannot host two classes at the same time |
| No Faculty Double-Booking | A faculty member cannot teach two classes simultaneously |
| No Section Conflict | A student section cannot have overlapping classes |
| Faculty Load Limits | Total scheduled hours must not exceed DesignationConstraint.maxLectureHours + maxLabHours |
| Consecutive Class Gap | A 55-minute gap is enforced between back-to-back classes for the same faculty |
| Semester Break Isolation | Break slots (e.g., 13:00–14:00 for Semesters 1–2) are never used for teaching |
| Room-Type Matching | Labs are assigned only to LAB rooms; Lectures/Tutorials to CLASSROOM/THEORY rooms |
| Room Capacity | Room capacity must meet or exceed the section's student count |
Generation is section-first: each section fully secures its lab slots before the next section begins, preventing partial slot starvation across sections.
┌────────────────────────────────────────────┐
│ React SPA (Port 3000) │
│ Admin Dashboard │ Faculty View │ Login │
└──────────────────────┬─────────────────────┘
│ REST / JSON
▼
┌────────────────────────────────────────────┐
│ Spring Boot API (Port 8080) │
│ │
│ ┌──────────────┐ ┌────────────────────┐ │
│ │ Controller │──▶│ Service Layer │ │
│ │ Layer (14) │ │ ┌─────────────┐ │ │
│ └──────────────┘ │ │ Generation │ │ │
│ │ │ Engine │ │ │
│ ┌──────────────┐ │ └─────────────┘ │ │
│ │ Spring │ │ ┌─────────────┐ │ │
│ │ Security │ │ │ Report │ │ │
│ │ (JWT/Basic) │ │ │ Service │ │ │
│ └──────────────┘ │ └─────────────┘ │ │
│ └────────┬───────────┘ │
│ │ │
│ ┌───────────────────────────▼───────────┐ │
│ │ Repository Layer (Spring Data) │ │
│ └───────────────────────────┬───────────┘ │
└──────────────────────────────┼─────────────┘
│ JDBC / HikariCP
▼
┌────────────────────────────────────────────┐
│ PostgreSQL (schema-versioned │
│ via Flyway — 10 migrations) │
└────────────────────────────────────────────┘
The backend is a strict three-layer monolith (Controller → Service → Repository). There is no shared state between layers and no service-to-controller call-backs, making the dependency graph acyclic and testable.
The core domain is built around 9 aggregate entities. The diagram below shows cardinality:
Department ─┬──< Section ──< CourseOffering >──┬── Course
│ │ │
└──< Faculty >────────┘ └─< DepartmentCourse
│
├──< FacultyPreference >── Course
└── DesignationConstraint (by designation)
CourseOffering ──< ScheduledClass >── TimeSlot
└── Room
| Entity | Responsibility |
|---|---|
Course |
Defines lectureHours, tutorialHours, practicalHours, and courseType |
CourseOffering |
Binds a Course to a Faculty and a Section — the unit the engine schedules |
Section |
A student cohort, scoped by department, semester, and year |
Faculty |
Linked to a User account; holds a designation that maps to load limits |
DesignationConstraint |
Per-designation caps on maxLectureHours, maxLabHours, maxTotalHours |
TimeSlot |
A named slot (dayOfWeek, startTime, endTime, isBreak, semesterGroup) |
Room |
Physical space with type (LAB, CLASSROOM) and capacity |
ScheduledClass |
The output record — binds an offering to a room and time slot |
TimetableMetadata |
Key-value store for publish state (DRAFT / PUBLISHED) |
TimetableGenerationService is the core of the system. It implements a greedy constraint-propagation algorithm with deterministic ordering.
generateTimetable(parity)
│
├─ 1. Delete existing classes for the target semester parity
├─ 2. Load all CourseOfferings, Rooms, and TimeSlots into memory
├─ 3. Partition TimeSlots into:
│ • allByGroup — all slots (break + teaching), keyed by semesterGroup × day
│ • teachingByGroup — teaching-only slots, keyed by semesterGroup × day
│
├─ 4. Sort sections: semesterGroup < semester < sectionId
│
└─ 5. For each section (section-first strategy):
├─ Sort offerings: practicalHours DESC, creditHours DESC
│
├─ For each offering:
│ ├─ schedulePractical() → greedy block assignment over allByGroup
│ ├─ scheduleComponent("LECTURE") → greedy over teachingByGroup
│ └─ scheduleComponent("TUTORIAL") → greedy over teachingByGroup
│
└─ Each scheduling call fills `occupied` and `facultyHours` incrementally
Every candidate TimeSlot block is validated through a pipeline of guards before a room assignment is attempted:
isContiguous(block)— blocks must form a gapless sequence (no break slot inside a practical block)isBreakForSemester(block, semester)— rejects slots overlapping institutional break windowsisSaturdayAfterLunch(block)— Saturday post-13:00 slots are excludedhasConsecutiveFacultyClass(...)— ensures the 55-minute gap by checking the immediately adjacent slots indaySlotsresourcesFree(...)— atomic check: room not occupied + faculty not occupied + section not occupied + faculty within load limits
The engine partitions semesters into two groups to support half-semester regeneration without disrupting already-published schedules:
| Parity | Semesters |
|---|---|
ODD |
1, 3, 5, 7 |
EVEN |
2, 4, 6, 8 |
Each parity maps to a semesterGroup string (e.g., SEM_1_2, SEM_3_4) which indexes the pre-partitioned TimeSlot lookup maps.
If the primary faculty cannot be scheduled for a practical due to load limits or slot exhaustion, the engine iterates FacultyPreference entries (ordered by priority) and attempts substitution with an alternate faculty member before failing.
A successful run produces a list of ScheduledClass records, each atomically persisted within the enclosing @Transactional boundary. On failure, the entire transaction rolls back.
All admin endpoints are protected. Faculty-facing endpoints are read-only.
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/admin/timetable/generate?parity=ODD|EVEN |
Run the scheduling engine |
GET |
/api/admin/timetable |
Retrieve the full current timetable |
GET |
/api/admin/timetable/section/{sectionId} |
Section-specific view |
GET |
/api/admin/timetable/faculty/{facultyId} |
Faculty-specific view |
POST |
/api/admin/timetable/update-slot |
Move a class (drag-and-drop) with re-validation |
POST |
/api/admin/timetable/publish |
Promote status from DRAFT → PUBLISHED |
GET |
/api/admin/timetable/status |
Get current publish state |
GET |
/api/admin/timetable/download/{sectionId}?type=xlsx |
Export timetable as Excel |
| Domain | Base Path | Operations |
|---|---|---|
| Courses | /api/admin/courses |
CRUD, department assignment |
| Faculty | /api/admin/faculty |
CRUD, designation, department scoping |
| Rooms | /api/admin/rooms |
CRUD |
| Sections | /api/admin/sections |
CRUD |
| Time Slots | /api/admin/time-slots |
CRUD, semester group tagging |
| Departments | /api/admin/departments |
CRUD |
| Designations | /api/admin/designations |
CRUD, hour limit configuration |
| Course Offerings | /api/admin/offerings |
Assign faculty to course + section |
| Faculty Preferences | /api/admin/preferences |
Priority-ranked course preferences |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/auth/login |
Returns session / token |
GET |
/api/faculty/timetable |
Faculty's own schedule (authenticated) |
Schema is version-controlled through Flyway (10 migrations, V1 through V10).
designation_constraints courses rooms
────────────────────── ────────────────── ─────────────────
designation (PK) course_id (PK) room_id (PK)
max_lecture_hours course_code room_number
max_lab_hours course_name type
max_total_hours credit_hours capacity
priority_level lecture_hours
tutorial_hours time_slots
departments practical_hours ─────────────────
────────────────────── course_type time_slot_id (PK)
department_id (PK) day_of_week
name sections start_time
────────────────── end_time
users section_id (PK) is_break
────────────────────── department_id (FK) semester_group
user_id (PK) name
email semester
password_hash year
role student_count
faculty
──────────────────────
faculty_id (PK)
user_id (FK)
department_id (FK)
designation (FK → designation_constraints)
first_name / last_name
course_offerings scheduled_classes faculty_preferences
────────────────────── ────────────────── ─────────────────
offering_id (PK) class_id (PK) preference_id (PK)
course_id (FK) offering_id (FK) faculty_id (FK)
faculty_id (FK) room_id (FK) course_id (FK)
section_id (FK) day_of_week priority
start_time / end_time
- Spring Security with form-based or token authentication.
- Role separation:
ADMINcan generate, configure, and publish;FACULTYrole has read-only access to their own timetable. - Passwords are stored as hashed values (
password_hash). - A
requires_password_resetflag forces credential rotation on first login.
| Layer | Technology | Version |
|---|---|---|
| Language | Java | 17 |
| Framework | Spring Boot | 3.5.7 |
| ORM | Hibernate / Spring Data JPA | 6.6 |
| Database | PostgreSQL | 12+ |
| Migrations | Flyway | 10.15 |
| Security | Spring Security | 6.5 |
| Excel Export | Apache POI | 5.3 |
| PDF Export | OpenPDF | 2.0 |
| CSV | Apache Commons CSV | 1.12 |
| Connection Pool | HikariCP | 6.3 |
| Build | Maven | 3.x |
| Frontend | React | 18 |
| Styling | Tailwind CSS | 3.x |
| Routing | React Router | 6.x |
| Test DB | H2 (in-memory) | 2.3 |
- Java 17+
- Maven 3.x
- PostgreSQL 12+
- Node.js 18+ and npm
# 1. Create the database
psql -U postgres -c "CREATE DATABASE schedule_planner;"
# 2. Configure credentials
cp backend/src/main/resources/application.properties.example \
backend/src/main/resources/application.properties
# Edit the datasource URL, username, and password
# 3. Start the API (Flyway runs migrations automatically)
cd backend
./mvnw spring-boot:run
# API available at http://localhost:8080cd frontend
npm install
npm start
# UI available at http://localhost:3000cd backend
mvn test # Integration + unit tests against H2 in-memory DB
mvn clean package # Full build + testsThe backend ships with a two-stage Dockerfile that keeps the final image lean — the Maven build happens in a temporary builder layer; only the compiled JAR is copied into the runtime image.
# Stage 1 — Build (Maven + JDK 17)
FROM maven:3.9.6-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests
# Stage 2 — Runtime (JRE only)
FROM eclipse-temurin:17-jdk
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]cd backend
docker build -t timetable-api:latest .The application reads datasource configuration from environment variables at runtime, so no credentials are baked into the image:
docker run -d \
--name timetable-api \
-p 8080:8080 \
-e SPRING_DATASOURCE_URL=jdbc:postgresql://<host>:5432/schedule_planner \
-e SPRING_DATASOURCE_USERNAME=<user> \
-e SPRING_DATASOURCE_PASSWORD=<password> \
timetable-api:latestFlyway migrations run automatically on startup — no manual schema setup required beyond creating the empty database.
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: schedule_planner
POSTGRES_USER: timetable
POSTGRES_PASSWORD: secret
volumes:
- pg_data:/var/lib/postgresql/data
ports:
- "5432:5432"
api:
image: timetable-api:latest
depends_on:
- db
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/schedule_planner
SPRING_DATASOURCE_USERNAME: timetable
SPRING_DATASOURCE_PASSWORD: secret
ports:
- "8080:8080"
volumes:
pg_data:docker compose up -dmini_project/
├── backend/
│ ├── src/main/java/com/timetable/timetable_api/
│ │ ├── config/ # Security, CORS
│ │ ├── controller/ # 14 REST controllers
│ │ ├── dto/ # Request/response objects (no entity leakage)
│ │ ├── model/ # 14 JPA entities
│ │ ├── repository/ # Spring Data repositories
│ │ └── service/ # Business logic & scheduling engine
│ ├── src/main/resources/
│ │ └── db/migration/ # Flyway SQL migrations (V1–V10)
│ └── src/test/ # Integration tests (H2)
│
└── frontend/
└── src/
├── components/ # Reusable UI components
└── pages/ # Route-level page components