FLIGHT RESERVATION SYSTEM (1st Semester Java Project)

Airline Reservation System — Hassan Bukhari
Project Walkthrough

Console-Based Airline
Reservation System

A complete breakdown of how this Java project works: the data structures, boot sequence, file persistence, and the logic inside every feature method.

Java Console App File I/O COMSATS Islamabad

First semester project: Programming Fundamentals (documentation written later)

What the program does

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.

Flight Listing
Shows all flights with ID, route, price, and live seat availability count.
Seat Booking
Visual seat grid. User picks a seat, enters name, a ticket file is generated.
Cancellation
Frees the seat in memory and removes the record from the bookings file safely.
Admin Panel
Password-protected. Add or remove flights. Both files are updated accordingly.
File Persistence
Plain CSV files under Data/. No external libraries or database.
Input Validation
Seat range, duplicate IDs, empty names, bad CSV lines all handled cleanly.

Project skeleton and state

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.

Java
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";
Why static arrays? For a first-semester project with no database, parallel arrays are the clearest approach. Each index 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).

The boot sequence

Three calls in main(). That is the entire startup.

Java
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
}
console output
$ java Project
Flights loaded successfully.
Bookings replayed onto seat map.

=============================
| FLIGHT RESERVATION SYSTEM |
=============================
1. View all flights
2. Book ticket
3. Cancel ticket
4. Admin login
5. Exit
Enter your choice: _

Loading flights

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'.

Data/flights.txt
FLIGHT_ID,SOURCE,DEST,PRICE
PK101,ISB,LHE,7500
PK102,ISB,KHI,12000
PK103,LHE,KHI,9500
Java
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.


Loading bookings

After all flights are in memory with all seats open, this method replays past bookings by marking the correct seats as 'X'.

Data/bookings.txt
FLIGHT_ID,NAME,SEAT
PK101,Ali,A1
PK101,Arham,B2
PK102,Sara,C3
Java — seat code to array index
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.


Displaying flights

Counts open seats live by scanning the seat array for each flight, then prints a formatted table using printf.

Java
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
PK101ISBLHE7,500 18
PK102ISBKHI12,000 20
PK103LHEKHI9,500 19

Booking a ticket

The most interactive method. It runs through five steps in sequence.

Step 1 and 2: Pick flight, view seat map

After the user enters a flight ID, the program prints a visual grid of the current seat status before asking which seat they want.

Before booking
 1  2 3 4
AOOOO
BOOOO
COOOO
DOOOO
EOOOO
After booking A1 and B3
 1  2 3 4
AXOOO
BOOXO
COOOO
DOOOO
EOOOO

Step 3: Validate the seat code

Java
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;
}

Step 4 and 5: Save and generate ticket

Java
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();
Flight Ticket
ISB → LHE
NAMEAli
FLIGHT IDPK101
SEATA1
PRICEPKR 7,500
FILEticket_PK101_A1.txt
Restart-safe: the seat is marked in memory and the record is written to disk. If the program restarts, loadBookings() will read that line and mark the seat X again. State is fully recovered.

Cancelling a booking

Two things must happen: free the seat in memory, and remove the exact line from bookings.txt without touching the rest of the file.

Free the seat

Java
if (seats[index][row][col] == 'O') {
    System.out.println("This seat is not currently booked.");
    return;
}
seats[index][row][col] = 'O';

Temp-file swap to remove the record

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.

Java
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);
Why this is safe: the original file is untouched until the very last line. If something crashes mid-write, your data is still intact. Only after the temp file is fully written does the rename happen.

Admin panel

Password-protected entry. For a demo project this is fine. In production you would never hardcode credentials in source code.

Java
System.out.print("Enter admin password: ");
String pwd = scanner.nextLine();
if (!pwd.equals("admin123")) {
    System.out.println("Incorrect password."); return;
}
admin session
Enter admin password: admin123

Admin Menu
1. Add flight
2. Remove flight
3. Back to main menu
Enter choice: 1

Enter new Flight ID: PK104
Enter source: KHI
Enter destination: LHE
Enter price: 8500
Flight added.

Adding a flight

Validates inputs, checks for duplicate IDs, initializes the seat map, appends to flights.txt, then increments flightCount.

Java
// 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++;

Removing a flight

Three places to clean up: in-memory arrays, flights.txt, and all related records in bookings.txt.

Java — left-shift to fill the gap
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.


Validation and edge cases

Missing files
If either text file does not exist, the program prints a message and continues. No crash.
Bad CSV lines
Checks parts.length before accessing any index. Malformed rows are skipped silently.
Seat range
Validates A to E and 1 to 4 bounds. Anything outside gets a clear rejection message.
Duplicate IDs
Admin cannot add a flight whose ID already exists in the array.
Empty inputs
Name, source, destination all trim and check for blank before proceeding.
Already booked / not booked
Booking checks for X. Cancellation checks for O. Both directions guarded.

File formats

Data/flights.txt
FLIGHT_ID,SOURCE,DEST,PRICE
PK101,ISB,LHE,7500
PK102,ISB,KHI,12000
Data/bookings.txt
FLIGHT_ID,NAME,SEAT
PK101,Ali,A1
PK102,Sara,B3
Ticket files are generated automatically on every booking as Data/ticket_FLIGHTID_SEAT.txt. The filename includes both the flight ID and seat code so there is no naming collision possible.

Full execution map

Start └─ main() ├─ loadFlights() // build flights and seat maps in memory ├─ loadBookings() // mark previously booked seats as X └─ mainMenu() // infinite loop ├─ displayFlights() ├─ bookTicket() │ ├─ pick flight, show seat map │ ├─ validate seat and name │ ├─ mark X in seats[][][] │ ├─ append to bookings.txt │ └─ write ticket file ├─ cancelTicket() │ ├─ validate flight and seat │ ├─ mark O in seats[][][] │ └─ rewrite bookings.txt via temp-file swap └─ adminMenu() ├─ addFlight() // append to flights.txt └─ removeFlight() // shift arrays, rewrite both files

What this teaches

Arrays and 3D arrays
Practical model for flights and per-flight seating without any collections framework.
File I/O
Scanner, BufferedReader, FileWriter, PrintWriter, and the temp-file swap pattern.
Input validation
Guarding every user input before it touches your data structures or files.
Modular design
Each feature is its own method. The menu stays clean and each method has one job.
State and persistence
Memory for the current session, files for across restarts. Two layers working together.
Platform independence
Using File.separator means paths work on Windows, Linux, and Mac without changes.

If you are just starting out

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.