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
- Why This Page Exists
- Key Concepts
- Saving to a File
- Loading from a File
- Handling Fields with Commas
- Integrating File I/O into MVC
- Handling Multiple Object Types
- Error Handling Patterns
- Complete File Handler Example
- Quick Check
- Code Completion
- Spot the Error
- Predict the Output
- Practice Exercises
- 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:
FileWriter(FILE_PATH)opens (or creates) the fileBufferedWriterwraps it for efficient writing- The loop writes each student as one CSV line
writer.newLine()adds a line break after each record- The
tryblock 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:
- Check if the file exists – if not, return an empty list (first run)
- Open the file with
BufferedReader - Read one line at a time with
readLine()(returnsnullat end of file) - Split each line on commas to get the individual fields
- Parse fields into the correct types and create a
Studentobject - Add the student to the list
- The
tryblock 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.
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
-
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. -
CSV with three fields – Extend the Student example to include a
housefield (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
-
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.
-
Export to readable format – Add an “Export” button that writes the student list to a formatted text file (not CSV) like: ``` Student Report ==============
- Alice - Grade: 85
- Bob - Grade: 92 Total students: 2 ```
Challenge
- Multi-model persistence – Build an app that manages both
StudentandCourseobjects. Each course has a name and a list of enrolled student names. Save students tostudents.csvand courses tocourses.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
- Prerequisites: JavaFX Basics – event handling for save/load buttons
- Prerequisites: MVC Architecture – file I/O belongs in the Model layer
- Prerequisites: Encapsulation – getters for building CSV lines
- Related: Exception Handling – try-catch for IOExceptions
- Next: Structuring Your IA – putting all components together