Chapter 6 — Reps
Conditioning, not grading. AI is OFF. Files on disk are real artifacts — produce real ones, check them with your own eyes, and don’t let an AI explain them to you. The discipline you want is seeing what’s actually there.
You’ll create a few files in this chapter’s code/ directory. Keep them; some reps build on previous ones.
Rep 1 — Write, Read, Confirm
Create Rep1.java:
import java.nio.file.Files;
import java.nio.file.Path;
public class Rep1 {
public static void main(String[] args) throws Exception {
Path p = Path.of("hello.txt");
Files.writeString(p, "Grace and peace.\n");
String back = Files.readString(p);
System.out.println("round-trip ok: " + back);
System.out.println("file path: " + p.toAbsolutePath());
}
}
Run. Note where the file actually landed on disk (the absolute path). Open hello.txt in your editor and confirm it contains what you wrote.
Rep 2 — Read Line by Line
Create verses.txt by hand in your editor, with three lines:
For God so loved the world
that he gave his only Son
that whoever believes in him should not perish
Then Rep2.java:
import java.nio.file.*;
import java.util.List;
public class Rep2 {
public static void main(String[] args) throws Exception {
List<String> lines = Files.readAllLines(Path.of("verses.txt"));
for (int i = 0; i < lines.size(); i++) {
System.out.printf("%d: %s%n", i + 1, lines.get(i));
}
System.out.println("Total lines: " + lines.size());
}
}
Run. Confirm three lines numbered 1, 2, 3. Notice: readAllLines strips the trailing newline; the List<String> size is the line count.
Rep 3 — The Naive Split Bug
Create Rep3.java:
public class Rep3 {
public static void main(String[] args) {
String csv = "1,\"What is the chief end of man?\",\"To glorify God, and enjoy him.\"";
String[] parts = csv.split(",");
System.out.println("naive split got " + parts.length + " fields:");
for (int i = 0; i < parts.length; i++) {
System.out.println(" [" + i + "] " + parts[i]);
}
}
}
Run. Observe the answer field has been split at the comma after “God”, giving four fields instead of three. This is the bug the careful CSV parser exists to solve. Don’t fix it here — just see it.
Rep 4 — The Careful CSV Parser
Type out the SimpleCsv.parseLine method from §6.3 of the chapter into a file SimpleCsv.java. Yes, type it. Don’t copy-paste.
Then Rep4.java:
import java.util.List;
public class Rep4 {
public static void main(String[] args) {
String csv = "1,\"What is the chief end of man?\",\"To glorify God, and enjoy him.\"";
List<String> fields = SimpleCsv.parseLine(csv);
System.out.println("careful parse got " + fields.size() + " fields:");
for (int i = 0; i < fields.size(); i++) {
System.out.println(" [" + i + "] " + fields.get(i));
}
}
}
Confirm three fields, with the answer field intact (no quotes, but the embedded comma preserved).
Rep 5 — Round-Trip a Record
Create Rep5.java:
import java.nio.file.*;
import java.util.*;
public class Rep5 {
public record Entry(int number, String question, String answer) {}
public static void main(String[] args) throws Exception {
List<Entry> entries = List.of(
new Entry(1, "What is the chief end of man?",
"To glorify God, and enjoy him forever."),
new Entry(2, "What is the Word of God?",
"The Bible — the Old and New Testaments.")
);
// Write a tiny CSV
StringBuilder sb = new StringBuilder("number,question,answer\n");
for (Entry e : entries) {
sb.append(e.number()).append(",")
.append("\"").append(e.question().replace("\"", "\"\"")).append("\",")
.append("\"").append(e.answer().replace("\"", "\"\"")).append("\"\n");
}
Files.writeString(Path.of("rep5.csv"), sb.toString());
// Read it back
List<String> lines = Files.readAllLines(Path.of("rep5.csv"));
System.out.println("Wrote and re-read " + (lines.size() - 1) + " entries.");
for (int i = 1; i < lines.size(); i++) {
List<String> fields = SimpleCsv.parseLine(lines.get(i));
System.out.println(" " + fields);
}
}
}
Run. Confirm output matches input (modulo the explicit quoting). Open rep5.csv in a text editor and see the actual file format.
Rep 6 — Hand-Roll a JSON Writer
Type out JsonOut.java from §6.4 of the chapter. Then in Rep6.java:
import java.util.List;
public class Rep6 {
public static void main(String[] args) {
List<JsonOut.Entry> entries = List.of(
new JsonOut.Entry(1, "What is the chief end of man?",
"To glorify God, and enjoy him forever."),
new JsonOut.Entry(2, "What did Jesus say about \"loving your neighbor\"?",
"Love your neighbor as yourself.")
);
System.out.println(JsonOut.writeAll(entries));
}
}
Run. Inspect the output. Open a JSON validator in your browser (e.g., jsonlint.com) and paste the output to confirm it parses as valid JSON. Notice how the embedded double-quote in entry 2’s question was escaped.
Rep 7 — Atomic Write
Type out SafeWrite.java from §6.6. Then Rep7.java:
import java.nio.file.*;
public class Rep7 {
public static void main(String[] args) throws Exception {
Path target = Path.of("rep7-data.txt");
// First write — establishes the "original."
Files.writeString(target, "ORIGINAL CONTENTS\n");
System.out.println("Before atomic write: " + Files.readString(target).trim());
// Atomic write — replaces the file.
SafeWrite.writeAtomic(target, "NEW CONTENTS\n");
System.out.println("After atomic write: " + Files.readString(target).trim());
// Sanity check: no .tmp file left over.
Path tmp = target.resolveSibling(target.getFileName() + ".tmp");
System.out.println("Temp file remaining? " + Files.exists(tmp));
}
}
Run. Confirm the contents changed and no .tmp file is left behind.
(Mental experiment: imagine Files.writeString(tmp, ...) threw halfway through. The original file would still say ORIGINAL CONTENTS. That’s the protection you bought.)
Rep 8 — Try-with-Resources
Create a file big.txt with five lines of any text. Then Rep8.java:
import java.io.BufferedReader;
import java.nio.file.*;
public class Rep8 {
public static void main(String[] args) throws Exception {
try (BufferedReader r = Files.newBufferedReader(Path.of("big.txt"))) {
String line;
int count = 0;
while ((line = r.readLine()) != null) {
count++;
System.out.println(count + ": " + line);
}
System.out.println("Read " + count + " lines.");
}
// BufferedReader auto-closed here.
}
}
Run. Notice no explicit r.close() call — the try-with-resources block handles it. As a deliberate experiment: try to use r after the try block. Note the compile error — r is out of scope.
Rep 9 — Validate at the Door
Create Rep9.java:
public class Rep9 {
public record Entry(int number, String question, String answer) {
public Entry {
if (number < 1) throw new IllegalArgumentException("number");
if (question == null || !question.endsWith("?"))
throw new IllegalArgumentException("question must end with ?");
if (answer == null || answer.isBlank())
throw new IllegalArgumentException("answer must be non-blank");
}
}
public static void main(String[] args) {
Entry ok = new Entry(1, "What is the chief end of man?", "To glorify God.");
System.out.println("OK: " + ok);
try {
Entry bad = new Entry(2, "no question mark here", "valid answer");
} catch (IllegalArgumentException ex) {
System.out.println("Caught (as expected): " + ex.getMessage());
}
try {
Entry bad = new Entry(0, "What?", "Empty number test.");
} catch (IllegalArgumentException ex) {
System.out.println("Caught (as expected): " + ex.getMessage());
}
}
}
Run. Confirm the first entry succeeds and the bad ones throw with named reasons. Note that the bad entries never exist — the constructor refused to build them. That’s the strongest form of validation.
Rep 10 — UTF-8 Discipline
Create Rep10.java:
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class Rep10 {
public static void main(String[] args) throws Exception {
String text = "Coram Deo — befóre Gód.\n"; // accented chars
Path p = Path.of("rep10.txt");
Files.writeString(p, text, StandardCharsets.UTF_8);
String back = Files.readString(p, StandardCharsets.UTF_8);
System.out.println("Wrote: " + text);
System.out.println("Read: " + back);
System.out.println("Bytes on disk: " + Files.size(p));
}
}
Run. The accented characters should round-trip cleanly. Notice the file size — each accented character takes more than one byte (UTF-8 uses multi-byte sequences for non-ASCII).
Rep 11 — Break It On Purpose
Six file-handling bug patterns. Apply, observe, fix.
- Use
Path.of("../somewhere/file.txt")and run from a directory where the relative path doesn’t resolve. Note theNoSuchFileException. - Write to a file inside a directory that doesn’t exist. Note the
NoSuchFileExceptionfrom the missing parent. - Try to read a directory as if it were a file. Note the error.
- Write a CSV line containing a literal double-quote without escaping it. Read it back with the careful parser. Note the misparse.
Files.write(p, ...)to a file that already exists. Confirm it truncates without warning.- Open a
BufferedReaderoutside try-with-resources. Forget to close it. Run a long-lived loop. (You probably won’t see the issue on a small example — but understand that the file handle is leaking.)
Done? One Last Thing.
Build the smallest end-to-end pipeline you can. From scratch, no looking:
- A
catechism.csvfile with 3 entries you write by hand. - A
Pipeline.javathat reads the CSV, validates each entry, and writes acatechism.jsonfile via atomic write. - Open both files in your editor. Confirm by eye that the JSON is correct.
- Delete
catechism.json. Re-run. Confirm it regenerates identically.
That’s the whole shape of Project 6. If this rep works, you’re ready.
Up next: Project 6 — Project 6: The Catechism Data Pipeline.