A complete breakdown of how this Java project works: the data structures, boot sequence, file persistence, and the logic inside every feature method.
First semester project: Programming Fundamentals (documentation written later)
A fully console-based reservation system with no database. Everything is persisted through plain text CSV files inside a Data/ folder, so data survives restarts. The program boots by loading that data into memory, then enters a menu loop.
Data/. No external libraries or database.All in-memory state lives at the class level as static arrays. The seat map is a 3D array indexed as seats[flightIndex][row][col]. Rows map to A through E, columns map to 1 through 4.
static final int MAX_FLIGHTS = 100; static final int ROWS = 5; // A to E static final int COLS = 4; // 1 to 4 static String[] flightIDs = new String[MAX_FLIGHTS]; static String[] sources = new String[MAX_FLIGHTS]; static String[] dests = new String[MAX_FLIGHTS]; static double[] prices = new double[MAX_FLIGHTS]; // seats[flightIndex][row][col] holds 'O' (open) or 'X' (booked) static char[][][] seats = new char[MAX_FLIGHTS][ROWS][COLS]; static int flightCount = 0; static Scanner scanner = new Scanner(System.in); // Platform-independent paths static final String DATA_DIR = "Data" + File.separator; static final String FLIGHTS_FILE = DATA_DIR + "flights.txt"; static final String BOOKINGS_FILE = DATA_DIR + "bookings.txt";
i represents one flight across all arrays simultaneously. The 3D char array for seats is the most interesting part: seats[i][row][col] gives direct access to any seat on any flight in O(1).
Three calls in main(). That is the entire startup.
public static void main(String[] args) { loadFlights(); // read Data/flights.txt into arrays loadBookings(); // read Data/bookings.txt and mark seats X mainMenu(); // enter the menu loop }
Reads Data/flights.txt line by line. Each line is CSV: FLIGHT_ID,SOURCE,DEST,PRICE. After parsing, every seat for that flight is initialized to 'O'.
Scanner fileScanner = new Scanner(file); while (fileScanner.hasNextLine() && flightCount < MAX_FLIGHTS) { String line = fileScanner.nextLine().trim(); if (line.isEmpty()) continue; String[] parts = line.split(","); if (parts.length < 4) continue; // skip malformed lines flightIDs[flightCount] = parts[0]; sources[flightCount] = parts[1]; dests[flightCount] = parts[2]; prices[flightCount] = Double.parseDouble(parts[3]); for (int i = 0; i < ROWS; i++) for (int j = 0; j < COLS; j++) seats[flightCount][i][j] = 'O'; // all open flightCount++; }
If the file does not exist, the program prints a message and continues. It does not crash. The Data/ directory is created via mkdirs() if missing.
After all flights are in memory with all seats open, this method replays past bookings by marking the correct seats as 'X'.
String seat = parts[2]; int row = seat.charAt(0) - 'A'; // 'A'=0, 'B'=1, ... int col = Integer.parseInt(seat.substring(1)) - 1; // "1"=0, "2"=1, ... if (row >= 0 && row < ROWS && col >= 0 && col < COLS) { seats[index][row][col] = 'X'; }
The char subtraction trick is clean: 'A' - 'A' = 0, 'B' - 'A' = 1, and so on. Same idea for the column number. After this method runs, the seat map perfectly reflects the last saved state.
Counts open seats live by scanning the seat array for each flight, then prints a formatted table using printf.
System.out.printf("%-10s %-10s %-10s %-10s %-10s%n", "ID", "From", "To", "Price", "Seats Left"); for (int i = 0; i < flightCount; i++) { int freeCount = 0; for (int r = 0; r < ROWS; r++) for (int c = 0; c < COLS; c++) if (seats[i][r][c] == 'O') freeCount++; System.out.printf("%-10s %-10s %-10s %-10.1f %-10d%n", flightIDs[i], sources[i], dests[i], prices[i], freeCount); }
| ID | From | To | Price (PKR) | Seats Left |
|---|---|---|---|---|
| PK101 | ISB | LHE | 7,500 | 18 |
| PK102 | ISB | KHI | 12,000 | 20 |
| PK103 | LHE | KHI | 9,500 | 19 |
The most interactive method. It runs through five steps in sequence.
After the user enters a flight ID, the program prints a visual grid of the current seat status before asking which seat they want.
int row = seat.charAt(0) - 'A'; int col; try { col = Integer.parseInt(seat.substring(1)) - 1; } catch (NumberFormatException e) { System.out.println("Invalid seat number."); return; } if (row < 0 || row >= ROWS || col < 0 || col >= COLS) { System.out.println("Seat out of range."); return; } if (seats[index][row][col] == 'X') { System.out.println("Seat already booked."); return; }
seats[index][row][col] = 'X'; // mark in memory // append to bookings.txt FileWriter writer = new FileWriter(BOOKINGS_FILE, true); writer.write(flightID + "," + name + "," + seat + "\n"); writer.close(); // generate ticket file PrintWriter ticket = new PrintWriter(ticketFileName); ticket.println("||| FLIGHT TICKET DETAILS |||"); ticket.println("Name: " + name); ticket.println("Flight ID: " + flightID); ticket.println("From: " + sources[index]); ticket.println("To: " + dests[index]); ticket.println("Seat: " + seat); ticket.println("Price: " + prices[index]); ticket.close();
loadBookings() will read that line and mark the seat X again. State is fully recovered.
Two things must happen: free the seat in memory, and remove the exact line from bookings.txt without touching the rest of the file.
if (seats[index][row][col] == 'O') { System.out.println("This seat is not currently booked."); return; } seats[index][row][col] = 'O';
You cannot delete a line from the middle of a text file in place. The standard solution is to write a new temp file with every line except the one you want gone, then replace the original.
File file = new File(BOOKINGS_FILE); File tempFile = new File(DATA_DIR + "temp_bookings.txt"); BufferedReader reader = new BufferedReader(new FileReader(file)); PrintWriter writer = new PrintWriter(new FileWriter(tempFile)); String line; while ((line = reader.readLine()) != null) { if (line.trim().isEmpty()) continue; String[] parts = line.split(","); if (parts[0].equals(flightID) && parts[2].equals(seat)) continue; // skip this one line writer.println(line); } reader.close(); writer.close(); if (file.delete()) tempFile.renameTo(file);
Password-protected entry. For a demo project this is fine. In production you would never hardcode credentials in source code.
System.out.print("Enter admin password: "); String pwd = scanner.nextLine(); if (!pwd.equals("admin123")) { System.out.println("Incorrect password."); return; }
Validates inputs, checks for duplicate IDs, initializes the seat map, appends to flights.txt, then increments flightCount.
// duplicate check for (int i = 0; i < flightCount; i++) if (flightIDs[i].equals(flightID)) { System.out.println("Flight ID already exists."); return; } // init seat map for (int i = 0; i < ROWS; i++) for (int j = 0; j < COLS; j++) seats[flightCount][i][j] = 'O'; // persist PrintWriter writer = new PrintWriter(new FileWriter(FLIGHTS_FILE, true)); writer.println(flightID + "," + src + "," + dst + "," + price); writer.close(); flightCount++;
Three places to clean up: in-memory arrays, flights.txt, and all related records in bookings.txt.
for (int i = index; i < flightCount - 1; i++) { flightIDs[i] = flightIDs[i + 1]; sources[i] = sources[i + 1]; dests[i] = dests[i + 1]; prices[i] = prices[i + 1]; seats[i] = seats[i + 1]; } flightCount--;
Then both files are rewritten using the same temp-file swap pattern from cancellation: skip lines that belong to the removed flight, write everything else, swap.
parts.length before accessing any index. Malformed rows are skipped silently.File.separator means paths work on Windows, Linux, and Mac without changes.Pick a small but realistic problem. Keep the data model simple. Wire persistence early so you can see your data survive a restart. That is the moment the project starts feeling like a real system instead of a homework exercise.
If you want the same structure: copy the method layout and evolve it feature by feature. loadFlights, loadBookings, displayFlights, bookTicket, cancelTicket, adminMenu, addFlight, removeFlight. One method at a time.