File I/O in Apps

IA Preparation: Without file I/O, your application loses all data when it closes. This page covers the practical patterns for loading and saving data in JavaFX IA projects.

Table of Contents

  1. Why This Page Exists
  2. Key Concepts
    1. CSV Format
    2. try-with-resources
  3. Saving to a File
    1. Writing an ArrayList to CSV
    2. Overwrite vs Append
  4. Loading from a File
    1. Reading CSV into an ArrayList
  5. Handling Fields with Commas
  6. Integrating File I/O into MVC
    1. Where Does File I/O Go?
    2. Adding Load/Save to the Model
    3. Controller: Load on Start, Save on Action
    4. Auto-Save on Close
  7. Handling Multiple Object Types
  8. Error Handling Patterns
    1. Catch in the Controller, Not the Model
    2. Handling Corrupt Data
  9. Complete File Handler Example
  10. Quick Check
  11. Code Completion
  12. Spot the Error
  13. Predict the Output
  14. Practice Exercises
    1. Core
    2. Extension
    3. Challenge
  15. Connections

Why This Page Exists

Your IA application needs persistence – when the user closes the app and reopens it later, their data should still be there. The simplest approach for an IA is reading and writing text files (usually CSV format).

This page covers:

  • Writing objects to a CSV file
  • Reading objects back from a CSV file
  • Integrating file I/O into an MVC application
  • Handling errors that occur during file operations

Key Concepts

CSV Format

CSV (Comma-Separated Values) stores each record on one line, with fields separated by commas.

Alice,85
Bob,92
Charlie,78

Each line represents one Student object. The first field is the name, the second is the grade. Your code splits each line on commas to reconstruct the object.

try-with-resources

Java’s try-with-resources syntax ensures that file readers and writers are always closed, even if an error occurs. This prevents data corruption and resource leaks.

try (BufferedWriter writer = new BufferedWriter(new FileWriter("data.csv"))) {
    writer.write("Alice,85");
    writer.newLine();
}  // writer is automatically closed here

Always use try-with-resources for file operations. Never leave a file open – if the program crashes, unsaved data can be lost or the file can become corrupted.


Saving to a File

Writing an ArrayList to CSV

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class StudentFileHandler {
    private static final String FILE_PATH = "students.csv";

    public static void save(ArrayList<Student> students) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_PATH))) {
            for (int i = 0; i < students.size(); i++) {
                Student s = students.get(i);
                writer.write(s.getName() + "," + s.getGrade());
                writer.newLine();
            }
        }
    }
}

What happens step by step:

  1. FileWriter(FILE_PATH) opens (or creates) the file
  2. BufferedWriter wraps it for efficient writing
  3. The loop writes each student as one CSV line
  4. writer.newLine() adds a line break after each record
  5. The try block automatically closes the writer when done

Overwrite vs Append

new FileWriter(path) overwrites the entire file each time. This is usually what you want – save the complete current state.

new FileWriter(path, true) appends to the existing file. Only use this if you are adding to a log file, not for saving application data.


Loading from a File

Reading CSV into an ArrayList

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.File;
import java.util.ArrayList;

public class StudentFileHandler {
    private static final String FILE_PATH = "students.csv";

    public static ArrayList<Student> load() throws IOException {
        ArrayList<Student> students = new ArrayList<>();
        File file = new File(FILE_PATH);

        if (!file.exists()) {
            return students;  // no file yet -- return empty list
        }

        try (BufferedReader reader = new BufferedReader(new FileReader(FILE_PATH))) {
            String line = reader.readLine();
            while (line != null) {
                String[] parts = line.split(",");
                if (parts.length == 2) {
                    String name = parts[0].trim();
                    int grade = Integer.parseInt(parts[1].trim());
                    students.add(new Student(name, grade));
                }
                line = reader.readLine();
            }
        }
        return students;
    }
}

What happens step by step:

  1. Check if the file exists – if not, return an empty list (first run)
  2. Open the file with BufferedReader
  3. Read one line at a time with readLine() (returns null at end of file)
  4. Split each line on commas to get the individual fields
  5. Parse fields into the correct types and create a Student object
  6. Add the student to the list
  7. The try block automatically closes the reader

Always check if the file exists before reading. On the first run, the file will not exist yet. Returning an empty list handles this gracefully.


Handling Fields with Commas

If a field might contain commas (e.g., an address), CSV breaks. The simplest solution for an IA is to use a different delimiter like | (pipe) or \t (tab).

// Writing with pipe delimiter
writer.write(s.getName() + "|" + s.getAddress() + "|" + s.getGrade());

// Reading with pipe delimiter
String[] parts = line.split("\\|");  // escape the pipe in regex

Integrating File I/O into MVC

Where Does File I/O Go?

File I/O belongs in the Model layer, not the Controller or View. Create a separate “file handler” class or add load/save methods to your collection model.

┌──────────────┐     ┌────────────┐     ┌──────────────────┐
│    View      │ ──  │ Controller │ ──  │     Model         │
│ (FXML + UI)  │     │ (handlers) │     │ StudentList       │
└──────────────┘     └────────────┘     │ StudentFileHandler│
                                        └──────────────────┘
                                              │
                                        ┌─────▼─────┐
                                        │ data.csv  │
                                        └───────────┘

Adding Load/Save to the Model

import java.util.ArrayList;
import java.io.IOException;

public class StudentList {
    private ArrayList<Student> students;

    public StudentList() {
        students = new ArrayList<>();
    }

    public void addStudent(Student s) { students.add(s); }

    public ArrayList<Student> getAll() { return students; }

    public int size() { return students.size(); }

    public void saveToFile() throws IOException {
        StudentFileHandler.save(students);
    }

    public void loadFromFile() throws IOException {
        students = StudentFileHandler.load();
    }
}

Controller: Load on Start, Save on Action

public class MainController {
    private StudentList model;

    @FXML private TextArea display;
    @FXML private Label status;

    public void setModel(StudentList model) {
        this.model = model;
        try {
            model.loadFromFile();
            status.setText("Loaded " + model.size() + " students.");
        } catch (IOException e) {
            status.setText("Could not load data: " + e.getMessage());
        }
        refreshDisplay();
    }

    @FXML
    private void handleSave() {
        try {
            model.saveToFile();
            status.setText("Saved " + model.size() + " students.");
        } catch (IOException e) {
            status.setText("Error saving: " + e.getMessage());
        }
    }
}

Auto-Save on Close

Save automatically when the user closes the window:

// In the Application class:
stage.setOnCloseRequest(event -> {
    try {
        model.saveToFile();
    } catch (IOException e) {
        Alert error = new Alert(Alert.AlertType.ERROR);
        error.setContentText("Could not save: " + e.getMessage());
        error.showAndWait();
        event.consume();  // prevent closing if save failed
    }
});

Handling Multiple Object Types

If your IA has more than one type of object, save each to a separate file.

public class DataManager {

    public static void saveAll(StudentList students, CourseList courses)
            throws IOException {
        StudentFileHandler.save(students.getAll());
        CourseFileHandler.save(courses.getAll());
    }

    public static void loadAll(StudentList students, CourseList courses)
            throws IOException {
        students.loadFromFile();
        courses.loadFromFile();
    }
}

One file per model class keeps things simple and avoids complex parsing.


Error Handling Patterns

Catch in the Controller, Not the Model

The Model’s saveToFile() and loadFromFile() should throw exceptions. The Controller should catch them and show the user an appropriate message.

// Model: throws the exception
public void saveToFile() throws IOException {
    StudentFileHandler.save(students);
}

// Controller: catches and displays feedback
@FXML
private void handleSave() {
    try {
        model.saveToFile();
        status.setText("Saved successfully.");
    } catch (IOException e) {
        status.setText("Error: " + e.getMessage());
    }
}

Why not catch in the Model? The Model does not know about the UI. It cannot display error messages or update labels. By throwing the exception, it lets the Controller decide how to inform the user.

Handling Corrupt Data

If a CSV line has the wrong number of fields or an unparseable value, skip it rather than crashing:

String line = reader.readLine();
while (line != null) {
    try {
        String[] parts = line.split(",");
        if (parts.length == 2) {
            String name = parts[0].trim();
            int grade = Integer.parseInt(parts[1].trim());
            students.add(new Student(name, grade));
        }
    } catch (NumberFormatException e) {
        // skip this line -- corrupted data
    }
    line = reader.readLine();
}

Complete File Handler Example

Here is a full StudentFileHandler with both save and load, suitable for an IA project.

import java.io.*;
import java.util.ArrayList;

public class StudentFileHandler {
    private static final String FILE_PATH = "students.csv";

    public static void save(ArrayList<Student> students) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_PATH))) {
            for (int i = 0; i < students.size(); i++) {
                Student s = students.get(i);
                writer.write(s.getName() + "," + s.getGrade());
                writer.newLine();
            }
        }
    }

    public static ArrayList<Student> load() throws IOException {
        ArrayList<Student> students = new ArrayList<>();
        File file = new File(FILE_PATH);

        if (!file.exists()) {
            return students;
        }

        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            String line = reader.readLine();
            while (line != null) {
                try {
                    String[] parts = line.split(",");
                    if (parts.length == 2) {
                        String name = parts[0].trim();
                        int grade = Integer.parseInt(parts[1].trim());
                        students.add(new Student(name, grade));
                    }
                } catch (NumberFormatException e) {
                    // skip corrupted line
                }
                line = reader.readLine();
            }
        }
        return students;
    }
}

Quick Check

Q1. What is the main benefit of using try-with-resources for file operations?

Q2. What does reader.readLine() return when it reaches the end of the file?

Q3. In an MVC application, where should file I/O code go?

Q4. What should the load method do if the data file does not exist yet?

Q5. What does "Alice,85".split(",") return?


Code Completion

Complete the save method for writing students to a CSV file.

Fill in the blanks: Complete this method that saves an ArrayList of Students to a file.

public static void save(ArrayList<Student> students) throws IOException {
    try (BufferedWriter writer = new BufferedWriter(
            new ("students.csv"))) {
        for (int i = 0; i < students.size(); i++) {
            Student s = students.get(i);
            writer.(s.getName() + "," + s.getGrade());
            writer.;
        }
    }
}

Spot the Error

This load method should read students from a file, but it only ever loads the first student.

Bug Hunt: This method reads from a CSV file but enters an infinite loop, repeatedly adding the first student.

1ArrayList<Student> students = new ArrayList<>(); 2BufferedReader reader = new BufferedReader(new FileReader("data.csv")); 3String line = reader.readLine(); 4while (line != null) { 5 String[] parts = line.split(","); 6 students.add(new Student(parts[0], Integer.parseInt(parts[1]))); 7 // missing something here 8}

Pick the correct fix for line 7:


Predict the Output

Predict: What does this code print after processing the CSV line?

String line = "Alice,85";
String[] parts = line.split(",");
Student s = new Student(parts[0], Integer.parseInt(parts[1]));
System.out.println(s);
// Student.toString() returns: name + " (" + grade + ")"

Practice Exercises

Core

  1. Save and load names – Create a simple app that lets the user add names to an ArrayList. Add a “Save” button that writes all names to names.txt (one per line) and a “Load” button that reads them back. Verify that closing and reopening the app preserves the names.

  2. CSV with three fields – Extend the Student example to include a house field (e.g., “Alice,85,Blue”). Update the save and load methods to handle the third field. Make sure the load method skips lines that do not have exactly 3 fields.

Extension

  1. Auto-save integration – Add auto-save to the Student Manager: save when the user adds or removes a student, and load when the app starts. Show the number of loaded records in the status label.

  2. Export to readable format – Add an “Export” button that writes the student list to a formatted text file (not CSV) like: ``` Student Report ==============

    1. Alice - Grade: 85
    2. Bob - Grade: 92 Total students: 2 ```

Challenge

  1. Multi-model persistence – Build an app that manages both Student and Course objects. Each course has a name and a list of enrolled student names. Save students to students.csv and courses to courses.csv. When loading, link students to courses by matching names. Handle the case where a student in a course file no longer exists in the student file.

Connections


© EduCS.me — A resource hub for IB Computer Science

This site uses Just the Docs, a documentation theme for Jekyll.