SECTION 1
Historical Context & Motivation
From the earliest days of computing, programs needed a way to preserve information after execution ended. Before the era of databases and cloud storage, the humble text file served as the primary medium for persisting data—configuration settings, scientific measurements, logs, and user records all lived as sequences of characters on disk. Understanding file I/O is not merely a historical curiosity; it remains a foundational skill because text files are human-readable, portable across operating systems, and require no special software to inspect. Even modern data-processing pipelines frequently begin and end with plain text formats like CSV, JSON, and TSV, making the ability to read from and write to files a prerequisite for virtually every domain of software engineering.
1956 • IBM 305 RAMAC
1969 • Unix & the File Abstraction
1995 • Java 1.0 — java.io
2004 • Java 5 — Scanner
2014 • Java 8 — Streams & NIO
The central question that text-file I/O addresses is deceptively simple: how does a program move structured data between volatile memory and persistent storage while keeping that data accessible to both humans and other programs? Answering this question requires understanding file paths, character encoding, buffered reading, parsing tokens, and the crucial practice of resource management—topics we explore in the sections that follow.
SECTION 2
Core Principles & Definitions
Working with text files in Java revolves around a small set of interconnected ideas. Each principle builds on the last: you must locate a file before you can open it, open it before you can read tokens, and close it before your program terminates cleanly. The following grid distills these foundational concepts.
File Path & the File Object
java.io.File) represents a path on disk. It does not open or read the file; it simply describes where the file is. Use relative paths (e.g., "data/scores.txt") for portability.Scanner for Reading
java.util.Scanner) wraps an input source and breaks it into tokens delimited by whitespace or a custom pattern. Methods like nextLine(), nextInt(), and nextDouble() parse the next token into the desired type.hasNext Guards
next*() method, you should verify data remains with hasNext(), hasNextLine(), or hasNextInt(). This prevents a NoSuchElementException at runtime.FileNotFoundException
try-catch block or by declaring throws FileNotFoundException on the enclosing method.Resource Cleanup
try-with-resources statement to guarantee automatic cleanup even if an exception is thrown.KEY TAKEAWAY
SECTION 3
Visual Explanation — The File-Reading Pipeline
The diagram below traces the complete lifecycle of reading a text file in Java, from the file system through the Scanner and into your program's data structures. Follow the arrows to see how raw bytes on disk become usable Java objects in memory.
Notice that the Scanner sits at the center of the pipeline. It acts as a translator: on its left side it consumes a stream of characters from disk, and on its right side it emits typed Java values—Strings, ints, doubles—ready for storage in arrays or ArrayList structures. The lifecycle summary at the bottom of the diagram is the pattern you will use in virtually every file-reading method you write for the AP exam: create, open, read, store, close.
SECTION 4
How It Works — Reading and Writing Code Patterns
Java's approach to file I/O is built on the principle of wrapping: you wrap a File object inside a Scanner to read, or inside a PrintWriter to write. This section presents the essential code patterns you will encounter on the AP Computer Science A exam, focusing on the Scanner-based reading pattern, followed by a brief discussion of writing with PrintWriter.
Pattern 1 — Reading Line by Line
The most common file-reading idiom uses a while loop guarded by hasNextLine(). On each iteration the loop calls nextLine() to retrieve one full line of text, which can then be split, parsed, or stored directly. This pattern is robust because it gracefully handles files of any length—including empty files, where the while condition is immediately false.
LINE-BY-LINE READING PATTERN
new File("data.txt") creates the path reference. hasNextLine() returns true while unread lines remain. nextLine() consumes and returns the next full line (up to but not including the newline character).
Pattern 2 — Reading Token by Token
When a file's data is whitespace-delimited (spaces, tabs, or newlines), you can use hasNext() with next() to read one token at a time, or hasNextInt() / nextInt() to read typed values. This is convenient when each token is a discrete datum, such as a list of integers or a name-score pair on each line.
TOKEN-BY-TOKEN READING PATTERN
next() reads the next whitespace-delimited String token. nextInt() reads the next token and parses it as an int. If the token is not an integer, an InputMismatchException is thrown.
Pattern 3 — Writing with PrintWriter
While the AP exam focuses primarily on reading, understanding writing solidifies the concept. A PrintWriter wraps a File and provides familiar print(), println(), and printf() methods. Like Scanner, it must be closed to flush and release resources. If the file does not exist, PrintWriter creates it; if it does exist, the file is overwritten unless you specifically append.
WRITING PATTERN
println() writes a line of text followed by a system-dependent newline. Always call close() to ensure all buffered data is actually written to disk.
Exception Handling Reminder
new Scanner(new File(...)) and new PrintWriter(new File(...)) throw a checked FileNotFoundException. On the AP exam, the simplest approach is to add throws FileNotFoundException to your method signature. In production code, a try-catch block or try-with-resources is preferred.SECTION 5
Parsing Strategies & Data Formats
Reading raw lines from a file is only half the battle. The other half involves parsing those lines into meaningful data. The strategy you choose depends on how the file is formatted. Below is a visual taxonomy of the most common text-file formats you will encounter on the AP exam and in real-world applications, followed by a detailed table.
split(","); whitespace-delimited files let Scanner do the work; fixed-width files rely on substring().| Format | Delimiter | Java Parsing Approach | When to Use |
|---|---|---|---|
| Whitespace | Spaces / tabs / newlines | Scanner.next(), nextInt(), etc. | Simple name-value pairs, integer lists |
| CSV | Comma (",") | line.split(",") → String array, then Integer.parseInt() | Spreadsheet exports, multi-field records |
| TSV | Tab ("\t") | line.split("\t") → same strategy as CSV | When data fields contain commas |
| One item per line | Newline | Scanner.nextLine() in a while loop | Word lists, log entries, sentences |
Common text file formats and their corresponding Java parsing strategies
SECTION 6
Worked Example — Reading a CSV Into an ArrayList
Suppose you have a file called students.csv containing one record per line in the format name,gradeLevel,gpa. Your task is to read the file, store all student names with a GPA above 3.5 into an ArrayList<String>, and print the result. The file contents are:
SAMPLE FILE: students.csv
Five records, each with a name (String), grade level (int), and GPA (double).
Filtering Students by GPA from a CSV File
- 1
Step 1 — Import Required Classes
You needjava.io.File,java.io.FileNotFoundException,java.util.Scanner, andjava.util.ArrayList. Declare your method withthrows FileNotFoundExceptionsince Scanner's File constructor throws a checked exception.public static void main(String[] args) throws FileNotFoundException - 2
Step 2 — Open the File with Scanner
Create a File object pointing to"students.csv", then wrap it in a Scanner. Also initialize the ArrayList that will hold the results.Scanner sc = new Scanner(new File("students.csv"));ArrayList<String> honors = new ArrayList<String>(); - 3
Step 3 — Loop and Parse Each Line
Use awhile (sc.hasNextLine())loop. Inside, callsc.nextLine()to get the entire line as a String, thenline.split(",")to break it into a String array of three parts. Parse the GPA withDouble.parseDouble(parts[2]).String[] parts = line.split(",");→ for the first line,parts = ["Alice", "12", "3.9"] - 4
Step 4 — Apply the Filter Condition
Check whether the parsed GPA exceeds 3.5. If so, addparts[0](the name) to the honors ArrayList.if (Double.parseDouble(parts[2]) > 3.5) { honors.add(parts[0]); } - 5
Step 5 — Close Scanner and Output Results
After the loop, close the Scanner to release the file handle. Then print the ArrayList. The expected output contains Alice (3.9), Carol (3.7), and Eve (3.6).sc.close();→ honors = [Alice, Carol, Eve]
The complete method is shown below for reference. Notice how concise the code is—barely ten lines of logic—yet it demonstrates every principle from Sections 2 through 5: creating a File, opening a Scanner, guarding with hasNextLine(), splitting a CSV line, converting types, filtering into a collection, and closing the resource.
COMPLETE METHOD
Output: [Alice, Carol, Eve]
SECTION 7
Strengths, Limitations & Common Pitfalls
Text files are a powerful and flexible data storage mechanism, but they are not without trade-offs. The table below contrasts the advantages of text-file I/O with its limitations, helping you decide when a text file is the right tool for the job—and when you should consider alternatives.
| Strengths | Limitations |
|---|---|
| Human-readable: you can open and inspect files in any text editor. | No built-in structure: you must write your own parsing logic for each format. |
| Platform-independent: text files work across Windows, macOS, and Linux. | Slow for large data: sequential reading is O(n) and lacks random access. |
| No special software required: no database engine, no binary decoder. | No type safety: all data is stored as characters and must be explicitly parsed. |
| Easy to produce: PrintWriter, System.out redirection, or even manual editing. | Delimiter conflicts: commas in CSV data can break naïve split() calls. |
| Great for small-to-medium datasets, configuration, and logging. | No concurrent access control: multiple writers can corrupt the file. |
Strengths vs. limitations of text file I/O
Common Pitfalls on the AP Exam
- Forgetting to handle FileNotFoundException — This is a checked exception. If you neither catch it nor declare it, the code will not compile.
- Mixing nextLine() with nextInt() — After
nextInt(), the newline character remains in the buffer. A subsequentnextLine()reads an empty string. Consume the leftover newline with an extranextLine()call. - Not closing the Scanner — While this may not crash a small program, it leaks OS resources and is considered a defect.
- Off-by-one with split() — Remember that
split(",")returns an array whose indices start at 0. If a line has 3 fields, the valid indices are 0, 1, and 2.
KEY TAKEAWAY
SECTION 8
Connection to Advanced Topics
The Scanner-based file reading you learn in AP Computer Science A is the entry point to a much larger ecosystem of I/O techniques. As you advance, you will encounter buffered streams, character encodings, binary file formats, and entire frameworks for serializing objects. The table below maps AP-level concepts to their more advanced counterparts.
| AP-Level Concept | Advanced Counterpart | Why It Matters |
|---|---|---|
Scanner for file reading | BufferedReader + Files.lines() (Java NIO) | Streams enable lazy, memory-efficient processing of massive files. |
PrintWriter for file writing | BufferedWriter + Files.write() | Buffered writers are significantly faster for high-throughput logging and data export. |
CSV parsing with split() | Libraries like Apache Commons CSV, Jackson CSV | Handle edge cases (quoted fields, embedded commas) that naïve splitting cannot. |
| Flat text files | JSON, XML, Protocol Buffers, databases | Structured formats support nesting, schema validation, and efficient querying. |
scanner.close() manually | try-with-resources (AutoCloseable) | Guarantees cleanup even when exceptions occur; standard in production code. |
AP concepts and their advanced counterparts
Mastering the fundamentals covered here—opening a resource, iterating through data, parsing tokens, and closing the resource—establishes a mental model that transfers directly to these advanced APIs. The specific class names change, but the underlying lifecycle pattern remains remarkably consistent across Java's entire I/O library and, indeed, across most programming languages.
SECTION 9
Practice Problems
Question 1: CONCEPTUAL
FileNotFoundException is a checked exception in Java?- A. Because file I/O operations are always slow and need special handling at runtime.
- B. Because the existence of a file on disk is an external condition the compiler cannot verify, so the programmer must acknowledge and handle the possibility of failure.
- C. Because unchecked exceptions cannot be thrown by constructors, and Scanner's constructor creates a file.
- D. Because FileNotFoundException extends RuntimeException, which Java requires to be caught.
Show answer (B)
throws, ensuring the failure path is explicitly handled. A is wrong because speed has nothing to do with checked vs. unchecked exceptions. C is wrong because unchecked exceptions can be thrown by constructors (e.g., NullPointerException), and Scanner's constructor doesn't create a file—it tries to open one. D is wrong because FileNotFoundException extends IOException, not RuntimeException; RuntimeExceptions are specifically unchecked.Question 2: BASIC
nums.txt containing:
10 20 30
What is the output of the following code?
Scanner sc = new Scanner(new File("nums.txt"));
int sum = 0;
while (sc.hasNextInt()) {
sum += sc.nextInt();
}
System.out.println(sum);
sc.close();- A. 10
- B. 30
- C. 60
- D. The code throws a NoSuchElementException.
Show answer (C)
hasNextInt() iterates three times, reading 10, 20, and 30. The sum is 10 + 20 + 30 = 60. A (10) would result if only one iteration occurred, which is not the case since all three tokens are valid integers. B (30) would result if only the last value were used or if the loop summed the first two and missed one—neither applies here. D is wrong because hasNextInt() returns false after the third integer, preventing any attempt to read past the end of the file.Question 3: INTERMEDIATE
data.csv contains:
red,5
blue,12
green,8
Consider the following code segment:
Scanner sc = new Scanner(new File("data.csv"));
ArrayList<String> result = new ArrayList<String>();
while (sc.hasNextLine()) {
String line = sc.nextLine();
String[] parts = line.split(",");
if (Integer.parseInt(parts[1]) > 6) {
result.add(parts[0]);
}
}
sc.close();
System.out.println(result);
What is printed?- A.
[red, blue, green] - B.
[blue, green] - C.
[blue] - D.
[red]
Show answer (B)
parts[1] is greater than 6. For "red,5", 5 > 6 is false. For "blue,12", 12 > 6 is true, so "blue" is added. For "green,8", 8 > 6 is true, so "green" is added. The result is [blue, green]. A is wrong because it includes "red", which fails the condition. C is wrong because it omits "green" (8 > 6 is true). D is wrong because "red" (5) does not satisfy the > 6 condition.Question 4: APPLIED
3 ptstemps.txt contains one double value per line representing daily high temperatures in degrees Fahrenheit. Write a static method averageTemp that takes a String parameter filename, reads all the temperatures from the file, and returns the average as a double. If the file is empty, return 0.0. You may assume the file exists.Show answer
public static double averageTemp(String filename) throws FileNotFoundException and creates a Scanner with new Scanner(new File(filename)). Initializes accumulator variables (e.g., double sum = 0.0; int count = 0;).
Point 2 — Correct loop reading and accumulating: Uses while (sc.hasNextDouble()) or while (sc.hasNextLine()) with Double.parseDouble() to read each temperature, adds it to sum, and increments count.
Point 3 — Correct return value and edge case: Closes the Scanner. Returns count == 0 ? 0.0 : sum / count to handle the empty-file case without division by zero.
Sample solution:
public static double averageTemp(String filename) throws FileNotFoundException {
Scanner sc = new Scanner(new File(filename));
double sum = 0.0;
int count = 0;
while (sc.hasNextDouble()) {
sum += sc.nextDouble();
count++;
}
sc.close();
if (count == 0) return 0.0;
return sum / count;
}Question 5: CRITICAL THINKING
4 ptsroster.csv with the format lastName,firstName,gradeLevel,gpa (one record per line, no header row). Write a class RosterAnalyzer with the following:
(a) A static method getHonorRoll(String filename, double minGPA) that returns an ArrayList<String> of full names (formatted as "firstName lastName") for all students whose GPA is at least minGPA.
(b) A static method writeHonorRoll(String inputFile, String outputFile, double minGPA) that calls getHonorRoll and writes each name on a separate line to outputFile using a PrintWriter.Show answer
hasNextLine(), calls split(",") to extract parts, and correctly identifies parts[0] as lastName, parts[1] as firstName, parts[3] as GPA (parsed with Double.parseDouble()).
Point 2 — getHonorRoll: Correct filtering and name formatting: Compares parsed GPA ≥ minGPA. Adds the correctly formatted name parts[1] + " " + parts[0] (firstName lastName) to the ArrayList. Returns the ArrayList. Closes Scanner.
Point 3 — writeHonorRoll: Correct use of PrintWriter: Creates a PrintWriter on the output file. Iterates through the ArrayList returned by getHonorRoll and writes each name with println(). Closes the PrintWriter.
Point 4 — Proper exception handling and resource management: Both methods declare throws FileNotFoundException. All Scanners and PrintWriters are closed. No resource leaks.
Sample solution:
import java.io.*;
import java.util.*;
public class RosterAnalyzer {
public static ArrayList<String> getHonorRoll(String filename, double minGPA)
throws FileNotFoundException {
Scanner sc = new Scanner(new File(filename));
ArrayList<String> result = new ArrayList<String>();
while (sc.hasNextLine()) {
String[] parts = sc.nextLine().split(",");
double gpa = Double.parseDouble(parts[3]);
if (gpa >= minGPA) {
result.add(parts[1] + " " + parts[0]);
}
}
sc.close();
return result;
}
public static void writeHonorRoll(String inputFile, String outputFile, double minGPA)
throws FileNotFoundException {
ArrayList<String> honors = getHonorRoll(inputFile, minGPA);
PrintWriter pw = new PrintWriter(new File(outputFile));
for (String name : honors) {
pw.println(name);
}
pw.close();
}
}SUMMARY
Summary
Summary
Text-file I/O in Java follows a consistent lifecycle: create a File object to locate the file on disk, wrap it in a Scanner to read and tokenize its contents, guard every read with hasNext-family methods to avoid exceptions, parse tokens into typed values using methods like Integer.parseInt() or Double.parseDouble(), store results in collections such as ArrayList, and always close the Scanner to release OS resources.
For files with comma-separated values, the split(",") method breaks each line into a String array, after which individual fields are accessed by index and converted as needed. The FileNotFoundException is a checked exception that must be declared or caught—Java's way of ensuring you plan for the real-world possibility that a file may not exist. Writing files mirrors reading: wrap a File in a PrintWriter, use println(), and close when done. Mastering these patterns equips you both for the AP exam and for the data-driven programming challenges that lie ahead.