Full application build guide

MediCare Hospital Patient Admission System

The complete, compiling application, built one real commit at a time. Every code block is the full file at that point in history, not an excerpt. Copy any block straight into your editor.

Checkpoints15
Source files6
Test files2
Tests14 passing
Java17+
CHECKPOINT 01

Project scaffold

pom.xml

A minimal Maven project: JUnit 5 for testing, the exec plugin so mvn exec:java runs the app directly.

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>medicare</groupId>
  <artifactId>medicare-patient-admission</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <junit.version>5.10.2</junit.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.2.5</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.2.0</version>
        <configuration>
          <mainClass>medicare.MediCareApp</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
Git checkpoint
git add pom.xml && git commit -m "chore: scaffold Maven project with JUnit 5"
CHECKPOINT 02

The PatientCategory enum

PatientCategory.java final

A one-shot file. There's no partial version of this worth committing separately, it's three names and nothing else.

src/main/java/medicare/PatientCategory.java
package medicare;

/**
 * The three categories MediCare Hospital admits patients under.
 * Only INPATIENT ever occupies a hospital bed.
 */
public enum PatientCategory {
    INPATIENT,
    OUTPATIENT,
    EMERGENCY
}
Git checkpoint
git add src/main/java/medicare/PatientCategory.java && git commit -m "feat: add PatientCategory enum"
CHECKPOINT 03

Patient: fields and constructor

Patient.java (v1)

Every field private. The constructor takes all seven values and assigns them directly, no validation yet, that comes next commit.

src/main/java/medicare/Patient.java
package medicare;

public class Patient {

    private final String patientId;
    private String firstName;
    private String lastName;
    private int age;
    private String gender;
    private String medicalCondition;
    private final PatientCategory category;

    public Patient(String patientId, String firstName, String lastName, int age,
                   String gender, String medicalCondition, PatientCategory category) {
        this.patientId = patientId;
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.gender = gender;
        this.medicalCondition = medicalCondition;
        this.category = category;
    }

    public String getPatientId() { return patientId; }
    public String getFirstName() { return firstName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }
    public String getLastName() { return lastName; }
    public void setLastName(String lastName) { this.lastName = lastName; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getGender() { return gender; }
    public void setGender(String gender) { this.gender = gender; }
    public String getMedicalCondition() { return medicalCondition; }
    public void setMedicalCondition(String medicalCondition) { this.medicalCondition = medicalCondition; }
    public PatientCategory getCategory() { return category; }
}
Git checkpoint
git add src/main/java/medicare/Patient.java && git commit -m "feat: add Patient class with encapsulated fields and constructor"
CHECKPOINT 04

Patient: validation and display

Patient.java (v2) final

setAge now guards against nonsense values, and the constructor routes through it so bad data can't sneak in at creation time either. displayDetails() prints one formatted row.

src/main/java/medicare/Patient.java
package medicare;

/**
 * Base class for every person MediCare Hospital registers.
 * Outpatient and Emergency patients are represented directly as Patient;
 * Inpatient extends this class to add bed information.
 */
public class Patient {

    private final String patientId;
    private String firstName;
    private String lastName;
    private int age;
    private String gender;
    private String medicalCondition;
    private final PatientCategory category;

    public Patient(String patientId, String firstName, String lastName, int age,
                   String gender, String medicalCondition, PatientCategory category) {
        this.patientId = patientId;
        this.firstName = firstName;
        this.lastName = lastName;
        setAge(age);
        this.gender = gender;
        this.medicalCondition = medicalCondition;
        this.category = category;
    }

    public String getPatientId() { return patientId; }
    public String getFirstName() { return firstName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }
    public String getLastName() { return lastName; }
    public void setLastName(String lastName) { this.lastName = lastName; }
    public int getAge() { return age; }

    public void setAge(int age) {
        if (age < 0 || age > 120) {
            throw new IllegalArgumentException("Age must be between 0 and 120.");
        }
        this.age = age;
    }

    public String getGender() { return gender; }
    public void setGender(String gender) { this.gender = gender; }
    public String getMedicalCondition() { return medicalCondition; }
    public void setMedicalCondition(String medicalCondition) { this.medicalCondition = medicalCondition; }
    public PatientCategory getCategory() { return category; }

    /**
     * Prints one row of patient details. Inpatient overrides this to append
     * ward and bed information after calling super.displayDetails().
     */
    public void displayDetails() {
        System.out.printf("%-8s %-12s %-12s %-4d %-8s %-18s %-10s%n",
                patientId, firstName, lastName, age, gender, medicalCondition, category);
    }
}
Common pitfallValidating in the setter but not routing the constructor through it. If the constructor writes this.age = age; directly, new Patient(..., -5, ...) slips straight past the guard.
Git checkpoint
git add src/main/java/medicare/Patient.java && git commit -m "feat: validate age and add displayDetails() to Patient"
CHECKPOINT 05

Inpatient extends Patient

Inpatient.java (v1)

The ward has one ward number, so it's a constant. Bed number starts null, a patient is registered before a bed exists for them.

src/main/java/medicare/Inpatient.java
package medicare;

public class Inpatient extends Patient {

    private static final int WARD_NUMBER = 1;

    private String bedNumber;

    public Inpatient(String patientId, String firstName, String lastName, int age,
                      String gender, String medicalCondition) {
        super(patientId, firstName, lastName, age, gender, medicalCondition, PatientCategory.INPATIENT);
        this.bedNumber = null;
    }

    public int getWardNumber() { return WARD_NUMBER; }
    public String getBedNumber() { return bedNumber; }
    public void setBedNumber(String bedNumber) { this.bedNumber = bedNumber; }
    public boolean hasBed() { return bedNumber != null; }
}
Git checkpoint
git add src/main/java/medicare/Inpatient.java && git commit -m "feat: add Inpatient class extending Patient via super()"
CHECKPOINT 06

Inpatient: override displayDetails()

Inpatient.java (v2) final

Calls super.displayDetails() first, then appends the bed line. A Patient-typed reference holding an Inpatient object still runs this version, that's polymorphism.

src/main/java/medicare/Inpatient.java
package medicare;

/**
 * A patient admitted to a hospital bed. The hospital has one ward, so
 * wardNumber is fixed; bedNumber stays null until a bed is allocated.
 */
public class Inpatient extends Patient {

    private static final int WARD_NUMBER = 1;

    private String bedNumber;

    public Inpatient(String patientId, String firstName, String lastName, int age,
                      String gender, String medicalCondition) {
        super(patientId, firstName, lastName, age, gender, medicalCondition, PatientCategory.INPATIENT);
        this.bedNumber = null;
    }

    public int getWardNumber() { return WARD_NUMBER; }
    public String getBedNumber() { return bedNumber; }
    public void setBedNumber(String bedNumber) { this.bedNumber = bedNumber; }
    public boolean hasBed() { return bedNumber != null; }

    @Override
    public void displayDetails() {
        super.displayDetails();
        String bedInfo = hasBed()
                ? "Ward " + WARD_NUMBER + ", Bed " + bedNumber
                : "Ward " + WARD_NUMBER + ", Bed: not allocated";
        System.out.println("   -> " + bedInfo);
    }
}
Git checkpoint
git add src/main/java/medicare/Inpatient.java && git commit -m "feat: override displayDetails() in Inpatient"
CHECKPOINT 07

A checked exception for a full ward

NoBedAvailableException.java final

Checked, on purpose: whoever calls allocateBed() has to either catch this or declare it. That's harder to ignore than a null return.

src/main/java/medicare/NoBedAvailableException.java
package medicare;

/**
 * Thrown when the ward has no free bed left to allocate.
 * Checked, on purpose: callers must decide what to do when the ward is full
 * instead of silently receiving null.
 */
public class NoBedAvailableException extends Exception {

    public NoBedAvailableException(String message) {
        super(message);
    }
}
Git checkpoint
git add src/main/java/medicare/NoBedAvailableException.java && git commit -m "feat: add NoBedAvailableException"
CHECKPOINT 08

Ward: the 4×5 bed grid

Ward.java (v1)

Two parallel grids: bedIds holds the fixed labels (B01 to B20), occupant holds either a patient ID or null. This checkpoint only builds and labels the grid.

src/main/java/medicare/Ward.java
package medicare;

public class Ward {

    private static final int ROWS = 4;
    private static final int COLS = 5;

    private final String[][] bedIds = new String[ROWS][COLS];
    private final String[][] occupant = new String[ROWS][COLS]; // patientId, or null if free

    public Ward() {
        initialiseBeds();
    }

    private void initialiseBeds() {
        int bedNumber = 1;
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                bedIds[row][col] = String.format("B%02d", bedNumber);
                bedNumber++;
            }
        }
    }
}
Git checkpoint
git add src/main/java/medicare/Ward.java && git commit -m "feat: initialise 4x5 ward bed grid"
CHECKPOINT 09

Ward: allocate and release beds

Ward.java (v2)

Allocation walks the grid until it finds a free cell. Release walks it again looking for the matching bed ID. Both reuse the same nested-loop shape from checkpoint 8.

src/main/java/medicare/Ward.java
package medicare;

public class Ward {

    private static final int ROWS = 4;
    private static final int COLS = 5;

    private final String[][] bedIds = new String[ROWS][COLS];
    private final String[][] occupant = new String[ROWS][COLS];

    public Ward() {
        initialiseBeds();
    }

    private void initialiseBeds() {
        int bedNumber = 1;
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                bedIds[row][col] = String.format("B%02d", bedNumber);
                bedNumber++;
            }
        }
    }

    /**
     * Finds the first free bed, assigns it to the patient, and returns the
     * bed ID. Throws NoBedAvailableException if the ward is full.
     */
    public String allocateBed(Inpatient patient) throws NoBedAvailableException {
        if (patient.hasBed()) {
            throw new IllegalStateException(
                    "Patient " + patient.getPatientId() + " already occupies bed " + patient.getBedNumber() + ".");
        }
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                if (occupant[row][col] == null) {
                    occupant[row][col] = patient.getPatientId();
                    patient.setBedNumber(bedIds[row][col]);
                    return bedIds[row][col];
                }
            }
        }
        throw new NoBedAvailableException("No beds available in the ward.");
    }

    /**
     * Frees the bed a patient occupies, if any.
     */
    public void releaseBed(Inpatient patient) {
        if (!patient.hasBed()) {
            return;
        }
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                if (bedIds[row][col].equals(patient.getBedNumber())) {
                    occupant[row][col] = null;
                }
            }
        }
        patient.setBedNumber(null);
    }
}
Common pitfallChecking occupant[row][col] == null only inside the inner loop and forgetting the return lets the search keep going and silently overwrite a later bed instead of stopping at the first free one.
Git checkpoint
git add src/main/java/medicare/Ward.java && git commit -m "feat: implement allocateBed() and releaseBed()"
CHECKPOINT 10

Ward: layout, availability and occupancy reports

Ward.java (v3) final

The final version of Ward. Everything below the exception handling is the same nested-loop shape once more, aimed at a different question each time.

src/main/java/medicare/Ward.java
package medicare;

/**
 * The hospital's one ward: 20 beds arranged in a 4x5 layout (B01-B20).
 * Tracks which bed each Inpatient occupies and reports on occupancy.
 */
public class Ward {

    private static final int ROWS = 4;
    private static final int COLS = 5;

    private final String[][] bedIds = new String[ROWS][COLS];
    private final String[][] occupant = new String[ROWS][COLS];

    public Ward() {
        initialiseBeds();
    }

    private void initialiseBeds() {
        int bedNumber = 1;
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                bedIds[row][col] = String.format("B%02d", bedNumber);
                bedNumber++;
            }
        }
    }

    public String allocateBed(Inpatient patient) throws NoBedAvailableException {
        if (patient.hasBed()) {
            throw new IllegalStateException(
                    "Patient " + patient.getPatientId() + " already occupies bed " + patient.getBedNumber() + ".");
        }
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                if (occupant[row][col] == null) {
                    occupant[row][col] = patient.getPatientId();
                    patient.setBedNumber(bedIds[row][col]);
                    return bedIds[row][col];
                }
            }
        }
        throw new NoBedAvailableException("No beds available in the ward.");
    }

    public void releaseBed(Inpatient patient) {
        if (!patient.hasBed()) {
            return;
        }
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                if (bedIds[row][col].equals(patient.getBedNumber())) {
                    occupant[row][col] = null;
                }
            }
        }
        patient.setBedNumber(null);
    }

    public void displayWardLayout() {
        System.out.println("Ward layout (4 x 5):");
        for (int row = 0; row < ROWS; row++) {
            StringBuilder line = new StringBuilder();
            for (int col = 0; col < COLS; col++) {
                String status = occupant[row][col] == null ? "FREE" : "OCC ";
                line.append(String.format("[%s:%s] ", bedIds[row][col], status));
            }
            System.out.println(line.toString().trim());
        }
    }

    public void displayAvailableBeds() {
        System.out.println("Available beds:");
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                if (occupant[row][col] == null) {
                    System.out.println("  " + bedIds[row][col]);
                }
            }
        }
    }

    public void displayOccupiedBeds() {
        System.out.println("Occupied beds:");
        for (int row = 0; row < ROWS; row++) {
            for (int col = 0; col < COLS; col++) {
                if (occupant[row][col] != null) {
                    System.out.println("  " + bedIds[row][col] + " -> Patient " + occupant[row][col]);
                }
            }
        }
    }

    public int totalBeds() { return ROWS * COLS; }

    public int countAvailableBeds() {
        int count = 0;
        for (String[] row : occupant) {
            for (String cell : row) {
                if (cell == null) {
                    count++;
                }
            }
        }
        return count;
    }

    public int countOccupiedBeds() { return totalBeds() - countAvailableBeds(); }

    public double occupancyPercentage() {
        return (countOccupiedBeds() / (double) totalBeds()) * 100.0;
    }
}
Common pitfallcountOccupiedBeds() / totalBeds() without the (double) cast performs integer division and always returns 0. The cast has to happen before the division, not after.
Git checkpoint
git add src/main/java/medicare/Ward.java && git commit -m "feat: add ward layout, availability and occupancy reports"
CHECKPOINT 11

App: menu skeleton

MediCareApp.java (v1)

Run the shell before filling it in. The switch statement exists, but most cases are stubs, run this once to prove the loop and the menu work before wiring in real logic.

src/main/java/medicare/MediCareApp.java
package medicare;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class MediCareApp {

    private final List<Patient> patients = new ArrayList<>();
    private final Ward ward = new Ward();

    public static void main(String[] args) {
        new MediCareApp().run();
    }

    public void run() {
        Scanner scanner = new Scanner(System.in);
        boolean running = true;

        while (running) {
            printMenu();
            System.out.print("Choose an option: ");
            int choice = Integer.parseInt(scanner.nextLine().trim());

            switch (choice) {
                case 13 -> running = false;
                default -> System.out.println("Not wired up yet.");
            }
        }

        System.out.println("Goodbye.");
        scanner.close();
    }

    private void printMenu() {
        System.out.println();
        System.out.println("========== MediCare Hospital Patient Admission System ==========");
        System.out.println(" 1.  Register patient");
        System.out.println(" 2.  Search for patient");
        System.out.println(" 3.  Update patient details");
        System.out.println(" 4.  Delete patient");
        System.out.println(" 5.  Display all patients");
        System.out.println(" 6.  Allocate bed to inpatient");
        System.out.println(" 7.  Release bed");
        System.out.println(" 8.  Display ward layout");
        System.out.println(" 9.  Display available beds");
        System.out.println("10.  Display occupied beds");
        System.out.println("11.  Display reports");
        System.out.println("12.  Sort patients");
        System.out.println("13.  Exit");
        System.out.println("==================================================================");
    }
}
Git checkpoint
git add src/main/java/medicare/MediCareApp.java && git commit -m "feat: add MediCareApp skeleton with menu loop"
CHECKPOINT 12

App: register patients

MediCareApp.java (v2)

registerPatient and findPatient are kept free of Scanner, so they can be unit tested directly. The interactive wrapper collects input and calls them.

src/main/java/medicare/MediCareApp.java (new methods added to v1)
public boolean registerPatient(Patient newPatient) {
    if (findPatient(newPatient.getPatientId()) != null) {
        return false;
    }
    patients.add(newPatient);
    return true;
}

public Patient findPatient(String patientId) {
    for (Patient p : patients) {
        if (p.getPatientId().equals(patientId)) {
            return p;
        }
    }
    return null;
}

private void registerPatientInteractive(Scanner scanner) {
    System.out.print("Patient ID: ");
    String id = scanner.nextLine().trim();

    if (findPatient(id) != null) {
        System.out.println("A patient with ID " + id + " already exists.");
        return;
    }

    System.out.print("First name: ");
    String firstName = scanner.nextLine().trim();
    System.out.print("Last name: ");
    String lastName = scanner.nextLine().trim();
    System.out.print("Age: ");
    int age = Integer.parseInt(scanner.nextLine().trim());
    System.out.print("Gender: ");
    String gender = scanner.nextLine().trim();
    System.out.print("Medical condition: ");
    String condition = scanner.nextLine().trim();

    System.out.println("Category: 1) Inpatient  2) Outpatient  3) Emergency");
    System.out.print("Choose category: ");
    int categoryChoice = Integer.parseInt(scanner.nextLine().trim());

    Patient patient = switch (categoryChoice) {
        case 1 -> new Inpatient(id, firstName, lastName, age, gender, condition);
        case 2 -> new Patient(id, firstName, lastName, age, gender, condition, PatientCategory.OUTPATIENT);
        case 3 -> new Patient(id, firstName, lastName, age, gender, condition, PatientCategory.EMERGENCY);
        default -> null;
    };

    if (patient == null) {
        System.out.println("Invalid category choice.");
        return;
    }

    registerPatient(patient);
    System.out.println("Patient " + id + " registered.");
}
Why split it this wayKeeping registerPatient(Patient) separate from registerPatientInteractive(Scanner) is what makes checkpoint 15's tests possible without simulating keyboard input.
Git checkpoint
git add src/main/java/medicare/MediCareApp.java && git commit -m "feat: implement patient registration with duplicate ID check"
CHECKPOINT 13

App: search, update, delete

MediCareApp.java (v3)

Delete has one extra responsibility: if the patient being removed is an Inpatient with a bed, release it first, or the bed grid would keep a phantom occupant.

src/main/java/medicare/MediCareApp.java (new methods added to v2)
public boolean updatePatient(String patientId, String firstName, String lastName,
                              int age, String gender, String medicalCondition) {
    Patient patient = findPatient(patientId);
    if (patient == null) {
        return false;
    }
    patient.setFirstName(firstName);
    patient.setLastName(lastName);
    patient.setAge(age);
    patient.setGender(gender);
    patient.setMedicalCondition(medicalCondition);
    return true;
}

public boolean deletePatient(String patientId) {
    Patient patient = findPatient(patientId);
    if (patient == null) {
        return false;
    }
    if (patient instanceof Inpatient inpatient && inpatient.hasBed()) {
        ward.releaseBed(inpatient);
    }
    return patients.remove(patient);
}

public void displayAllPatients() {
    if (patients.isEmpty()) {
        System.out.println("No patients registered yet.");
        return;
    }
    System.out.println("All registered patients:");
    for (Patient p : patients) {
        p.displayDetails();
    }
}

public int getPatientCount() { return patients.size(); }
public Ward getWard() { return ward; }
Git checkpoint
git add src/main/java/medicare/MediCareApp.java && git commit -m "feat: implement search, update and delete patient operations"
CHECKPOINT 14

App: beds, reports and sorting, fully wired

MediCareApp.java (v4) final

The complete, final file: every menu case now calls something real. This is what actually ships.

src/main/java/medicare/MediCareApp.java
package medicare;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;

/**
 * Console-based Hospital Patient Admission System for MediCare Hospital.
 * Manages patient records and one 20-bed ward, entirely in memory.
 */
public class MediCareApp {

    private final List<Patient> patients = new ArrayList<>();
    private final Ward ward = new Ward();

    public static void main(String[] args) {
        new MediCareApp().run();
    }

    public void run() {
        Scanner scanner = new Scanner(System.in);
        boolean running = true;

        while (running) {
            printMenu();
            int choice = readInt(scanner, "Choose an option: ");

            switch (choice) {
                case 1 -> registerPatientInteractive(scanner);
                case 2 -> searchPatientInteractive(scanner);
                case 3 -> updatePatientInteractive(scanner);
                case 4 -> deletePatientInteractive(scanner);
                case 5 -> displayAllPatients();
                case 6 -> allocateBedInteractive(scanner);
                case 7 -> releaseBedInteractive(scanner);
                case 8 -> ward.displayWardLayout();
                case 9 -> ward.displayAvailableBeds();
                case 10 -> ward.displayOccupiedBeds();
                case 11 -> displayReports();
                case 12 -> sortPatientsInteractive(scanner);
                case 13 -> running = false;
                default -> System.out.println("Invalid option, try again.");
            }
        }

        System.out.println("Goodbye.");
        scanner.close();
    }

    private void printMenu() {
        System.out.println();
        System.out.println("========== MediCare Hospital Patient Admission System ==========");
        System.out.println(" 1.  Register patient");
        System.out.println(" 2.  Search for patient");
        System.out.println(" 3.  Update patient details");
        System.out.println(" 4.  Delete patient");
        System.out.println(" 5.  Display all patients");
        System.out.println(" 6.  Allocate bed to inpatient");
        System.out.println(" 7.  Release bed");
        System.out.println(" 8.  Display ward layout");
        System.out.println(" 9.  Display available beds");
        System.out.println("10.  Display occupied beds");
        System.out.println("11.  Display reports");
        System.out.println("12.  Sort patients");
        System.out.println("13.  Exit");
        System.out.println("==================================================================");
    }

    public boolean registerPatient(Patient newPatient) {
        if (findPatient(newPatient.getPatientId()) != null) {
            return false;
        }
        patients.add(newPatient);
        return true;
    }

    public Patient findPatient(String patientId) {
        for (Patient p : patients) {
            if (p.getPatientId().equals(patientId)) {
                return p;
            }
        }
        return null;
    }

    public boolean updatePatient(String patientId, String firstName, String lastName,
                                  int age, String gender, String medicalCondition) {
        Patient patient = findPatient(patientId);
        if (patient == null) {
            return false;
        }
        patient.setFirstName(firstName);
        patient.setLastName(lastName);
        patient.setAge(age);
        patient.setGender(gender);
        patient.setMedicalCondition(medicalCondition);
        return true;
    }

    public boolean deletePatient(String patientId) {
        Patient patient = findPatient(patientId);
        if (patient == null) {
            return false;
        }
        if (patient instanceof Inpatient inpatient && inpatient.hasBed()) {
            ward.releaseBed(inpatient);
        }
        return patients.remove(patient);
    }

    public void displayAllPatients() {
        if (patients.isEmpty()) {
            System.out.println("No patients registered yet.");
            return;
        }
        System.out.println("All registered patients:");
        for (Patient p : patients) {
            p.displayDetails();
        }
    }

    public int getPatientCount() { return patients.size(); }
    public List<Patient> getPatients() { return patients; }
    public Ward getWard() { return ward; }

    public void sortBySurname() {
        patients.sort(Comparator.comparing(Patient::getLastName));
    }

    public void sortByPatientId() {
        patients.sort(Comparator.comparing(Patient::getPatientId));
    }

    public void displayReports() {
        System.out.println("---- Patient report ----");
        System.out.println("Total registered patients: " + getPatientCount());
        displayAllPatients();

        System.out.println();
        System.out.println("---- Bed occupancy report ----");
        System.out.println("Total beds:      " + ward.totalBeds());
        System.out.println("Available beds:  " + ward.countAvailableBeds());
        System.out.println("Occupied beds:   " + ward.countOccupiedBeds());
        System.out.printf("Occupancy:       %.1f%%%n", ward.occupancyPercentage());
    }

    private void registerPatientInteractive(Scanner scanner) {
        System.out.print("Patient ID: ");
        String id = scanner.nextLine().trim();

        if (findPatient(id) != null) {
            System.out.println("A patient with ID " + id + " already exists.");
            return;
        }

        System.out.print("First name: ");
        String firstName = scanner.nextLine().trim();
        System.out.print("Last name: ");
        String lastName = scanner.nextLine().trim();
        int age = readInt(scanner, "Age: ");
        System.out.print("Gender: ");
        String gender = scanner.nextLine().trim();
        System.out.print("Medical condition: ");
        String condition = scanner.nextLine().trim();

        System.out.println("Category: 1) Inpatient  2) Outpatient  3) Emergency");
        int categoryChoice = readInt(scanner, "Choose category: ");

        Patient patient;
        try {
            patient = switch (categoryChoice) {
                case 1 -> new Inpatient(id, firstName, lastName, age, gender, condition);
                case 2 -> new Patient(id, firstName, lastName, age, gender, condition, PatientCategory.OUTPATIENT);
                case 3 -> new Patient(id, firstName, lastName, age, gender, condition, PatientCategory.EMERGENCY);
                default -> null;
            };
        } catch (IllegalArgumentException e) {
            System.out.println("Could not register patient: " + e.getMessage());
            return;
        }

        if (patient == null) {
            System.out.println("Invalid category choice.");
            return;
        }

        registerPatient(patient);
        System.out.println("Patient " + id + " registered.");
    }

    private void searchPatientInteractive(Scanner scanner) {
        System.out.print("Patient ID to search for: ");
        String id = scanner.nextLine().trim();
        Patient patient = findPatient(id);
        if (patient == null) {
            System.out.println("No patient found with ID " + id + ".");
        } else {
            patient.displayDetails();
        }
    }

    private void updatePatientInteractive(Scanner scanner) {
        System.out.print("Patient ID to update: ");
        String id = scanner.nextLine().trim();
        Patient patient = findPatient(id);
        if (patient == null) {
            System.out.println("No patient found with ID " + id + ".");
            return;
        }

        System.out.print("First name [" + patient.getFirstName() + "]: ");
        String firstName = orDefault(scanner.nextLine(), patient.getFirstName());
        System.out.print("Last name [" + patient.getLastName() + "]: ");
        String lastName = orDefault(scanner.nextLine(), patient.getLastName());
        int age = readInt(scanner, "Age [" + patient.getAge() + "]: ");
        System.out.print("Gender [" + patient.getGender() + "]: ");
        String gender = orDefault(scanner.nextLine(), patient.getGender());
        System.out.print("Medical condition [" + patient.getMedicalCondition() + "]: ");
        String condition = orDefault(scanner.nextLine(), patient.getMedicalCondition());

        updatePatient(id, firstName, lastName, age, gender, condition);
        System.out.println("Patient " + id + " updated.");
    }

    private void deletePatientInteractive(Scanner scanner) {
        System.out.print("Patient ID to delete: ");
        String id = scanner.nextLine().trim();
        if (deletePatient(id)) {
            System.out.println("Patient " + id + " deleted.");
        } else {
            System.out.println("No patient found with ID " + id + ".");
        }
    }

    private void allocateBedInteractive(Scanner scanner) {
        System.out.print("Inpatient ID to allocate a bed to: ");
        String id = scanner.nextLine().trim();
        Patient patient = findPatient(id);

        if (!(patient instanceof Inpatient inpatient)) {
            System.out.println("No inpatient found with ID " + id + ".");
            return;
        }

        try {
            String bed = ward.allocateBed(inpatient);
            System.out.println("Allocated bed " + bed + " to patient " + id + ".");
        } catch (NoBedAvailableException e) {
            System.out.println("Cannot admit patient: " + e.getMessage());
        } catch (IllegalStateException e) {
            System.out.println(e.getMessage());
        }
    }

    private void releaseBedInteractive(Scanner scanner) {
        System.out.print("Inpatient ID to release a bed from: ");
        String id = scanner.nextLine().trim();
        Patient patient = findPatient(id);

        if (!(patient instanceof Inpatient inpatient)) {
            System.out.println("No inpatient found with ID " + id + ".");
            return;
        }

        ward.releaseBed(inpatient);
        System.out.println("Bed released for patient " + id + ".");
    }

    private void sortPatientsInteractive(Scanner scanner) {
        System.out.println("Sort by: 1) Surname  2) Patient ID");
        int choice = readInt(scanner, "Choose: ");
        if (choice == 1) {
            sortBySurname();
        } else {
            sortByPatientId();
        }
        displayAllPatients();
    }

    private static String orDefault(String input, String fallback) {
        return input.isBlank() ? fallback : input.trim();
    }

    private static int readInt(Scanner scanner, String prompt) {
        while (true) {
            System.out.print(prompt);
            String line = scanner.nextLine().trim();
            try {
                return Integer.parseInt(line);
            } catch (NumberFormatException e) {
                System.out.println("Please enter a whole number.");
            }
        }
    }
}
Git checkpoint
git add src/main/java/medicare/MediCareApp.java && git commit -m "feat: wire up bed allocation, release and sorting from the menu"
CHECKPOINT 15

The JUnit test suite

WardTest.java + MediCareAppTest.java final

14 tests across two files. Every one runs against the real classes above, no mocking. Confirmed green with junit-platform-console-standalone before this guide was published.

src/test/java/medicare/WardTest.java
package medicare;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class WardTest {

    private Ward ward;

    @BeforeEach
    void setUp() {
        ward = new Ward();
    }

    @Test
    void allocatesFirstFreeBedInReadingOrder() throws NoBedAvailableException {
        Inpatient patient = new Inpatient("P001", "Amy", "Ndlovu", 34, "F", "Flu");

        String bed = ward.allocateBed(patient);

        assertEquals("B01", bed);
        assertEquals("B01", patient.getBedNumber());
        assertEquals(19, ward.countAvailableBeds());
    }

    @Test
    void releaseBedFreesItForReuse() throws NoBedAvailableException {
        Inpatient first = new Inpatient("P001", "Amy", "Ndlovu", 34, "F", "Flu");
        ward.allocateBed(first);

        ward.releaseBed(first);

        assertFalse(first.hasBed());
        assertEquals(20, ward.countAvailableBeds());
    }

    @Test
    void throwsWhenAllTwentyBedsAreTaken() throws NoBedAvailableException {
        for (int i = 1; i <= 20; i++) {
            ward.allocateBed(new Inpatient("P" + i, "First" + i, "Last" + i, 30, "F", "Observation"));
        }

        Inpatient patient21 = new Inpatient("P021", "Twenty", "First", 40, "M", "Overflow");

        assertThrows(NoBedAvailableException.class, () -> ward.allocateBed(patient21));
    }

    @Test
    void occupancyPercentageReflectsOccupiedBeds() throws NoBedAvailableException {
        for (int i = 1; i <= 5; i++) {
            ward.allocateBed(new Inpatient("P" + i, "First" + i, "Last" + i, 30, "F", "Observation"));
        }

        assertEquals(25.0, ward.occupancyPercentage(), 0.001);
    }

    @Test
    void cannotAllocateASecondBedToTheSamePatient() throws NoBedAvailableException {
        Inpatient patient = new Inpatient("P001", "Amy", "Ndlovu", 34, "F", "Flu");
        ward.allocateBed(patient);

        assertThrows(IllegalStateException.class, () -> ward.allocateBed(patient));
    }
}
src/test/java/medicare/MediCareAppTest.java
package medicare;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class MediCareAppTest {

    private MediCareApp app;

    @BeforeEach
    void setUp() {
        app = new MediCareApp();
    }

    @Test
    void registersANewPatient() {
        Patient patient = new Patient("P001", "Amy", "Ndlovu", 34, "F", "Flu", PatientCategory.OUTPATIENT);

        boolean registered = app.registerPatient(patient);

        assertTrue(registered);
        assertEquals(1, app.getPatientCount());
    }

    @Test
    void duplicatePatientIdIsRejected() {
        Patient p1 = new Patient("P001", "Amy", "Ndlovu", 34, "F", "Flu", PatientCategory.OUTPATIENT);
        Patient p2 = new Patient("P001", "Tumi", "Sithole", 41, "M", "Asthma", PatientCategory.OUTPATIENT);

        app.registerPatient(p1);
        boolean secondRegistered = app.registerPatient(p2);

        assertFalse(secondRegistered);
        assertEquals(1, app.getPatientCount());
    }

    @Test
    void findsAPatientByIdAfterRegistration() {
        Patient patient = new Patient("P001", "Amy", "Ndlovu", 34, "F", "Flu", PatientCategory.OUTPATIENT);
        app.registerPatient(patient);

        Patient found = app.findPatient("P001");

        assertNotNull(found);
        assertEquals("Ndlovu", found.getLastName());
    }

    @Test
    void searchingForAnUnknownIdReturnsNull() {
        assertNull(app.findPatient("does-not-exist"));
    }

    @Test
    void updatesAnExistingPatientsDetails() {
        app.registerPatient(new Patient("P001", "Amy", "Ndlovu", 34, "F", "Flu", PatientCategory.OUTPATIENT));

        boolean updated = app.updatePatient("P001", "Amy", "Ndlovu", 35, "F", "Recovered");

        assertTrue(updated);
        assertEquals(35, app.findPatient("P001").getAge());
        assertEquals("Recovered", app.findPatient("P001").getMedicalCondition());
    }

    @Test
    void deletingAPatientRemovesThemFromTheList() {
        app.registerPatient(new Patient("P001", "Amy", "Ndlovu", 34, "F", "Flu", PatientCategory.OUTPATIENT));

        boolean deleted = app.deletePatient("P001");

        assertTrue(deleted);
        assertEquals(0, app.getPatientCount());
    }

    @Test
    void deletingAnInpatientAlsoReleasesTheirBed() throws NoBedAvailableException {
        Inpatient inpatient = new Inpatient("P001", "Amy", "Ndlovu", 34, "F", "Flu");
        app.registerPatient(inpatient);
        app.getWard().allocateBed(inpatient);

        app.deletePatient("P001");

        assertEquals(20, app.getWard().countAvailableBeds());
    }

    @Test
    void rejectsAnInvalidAge() {
        assertThrows(IllegalArgumentException.class,
                () -> new Patient("P001", "Amy", "Ndlovu", -5, "F", "Flu", PatientCategory.OUTPATIENT));
    }

    @Test
    void sortsPatientsBySurname() {
        app.registerPatient(new Patient("P002", "Ben", "Zulu", 40, "M", "Cold", PatientCategory.OUTPATIENT));
        app.registerPatient(new Patient("P001", "Amy", "Ndlovu", 34, "F", "Flu", PatientCategory.OUTPATIENT));

        app.sortBySurname();

        assertEquals("Ndlovu", app.getPatients().get(0).getLastName());
        assertEquals("Zulu", app.getPatients().get(1).getLastName());
    }
}
Git checkpoint
git add src/test && git commit -m "test: add JUnit tests for patient CRUD, bed allocation and validation"
RECAP

What the finished commit log looks like

If you commit at each checkpoint above, git log --oneline reads as a build order a marker (or a future you) can follow without watching the video again.

a1b2c3dtest: add JUnit tests for patient CRUD, bed allocation and validation
b2c3d4efeat: wire up bed allocation, release and sorting from the menu
c3d4e5ffeat: implement search, update and delete patient operations
d4e5f6afeat: implement patient registration with duplicate ID check
e5f6a7bfeat: add MediCareApp skeleton with menu loop
f6a7b8cfeat: add ward layout, availability and occupancy reports
a7b8c9dfeat: implement allocateBed() and releaseBed()
b8c9d0efeat: initialise 4x5 ward bed grid
c9d0e1ffeat: add NoBedAvailableException
d0e1f2afeat: override displayDetails() in Inpatient
e1f2a3bfeat: add Inpatient class extending Patient via super()
f2a3b4cfeat: validate age and add displayDetails() to Patient
a3b4c5dfeat: add Patient class with encapsulated fields and constructor
b4c5d6efeat: add PatientCategory enum
c5d6e7fchore: scaffold Maven project with JUnit 5
Verified before publishingAll 6 source files compile with javac and all 14 tests pass under junit-platform-console-standalone. Nothing in this guide is untested.