Study Notes
Data & I/Ofoundation

File Handling & XNews

Java I/O streams, FileReader/FileWriter, BufferedReader, the File class, reading & writing text files, and the XNews file-processing project.

30 min read·verified Content review 2026·reviewed 2026-08-30

After this you should be able to

  • Explain the core idea in File Handling & XNews
  • Apply File Handling & XNews in a representative exercise
File I/OBufferedReaderXNews

My study notes on reading/writing files with java.nio.file.Files, working with CSV, append modes & open options, and the foundations for building XNews (a personalized news fetcher using Jackson + OkHttp + NewsAPI).

java.nio.file.FilesBufferedReader/WriterStandardOpenOption CSV / RFC 4180JacksonOkHttp + REST
Section I

Notebook Summary — copy by hand#

Telegraphic, high-density. Each block is sized to transcribe fast. Tables for comparisons, "doodle:" lines for sketches. Everything here is the compressed form of Section II.

// FILE HANDLING — CORE
FILE HANDLING (java.nio.file.Files)
  why files? → data survives app close / power off (persistence)
  examples → media player reads video, spreadsheet opens CSV, app logs
  Path → Paths.get("output.txt")  // points at a file, no I/O yet

Files METHODS  (simple → complex)
  readAllBytes(path)    → byte[]            all bytes
  readAllLines(path)    → List<String>      all lines (UTF-8 default)
  write(path, byte[])   → write bytes       (default: CREATE+WRITE+TRUNCATE)
  write(path, lines)    → write text lines
  newBufferedReader(p)  → BufferedReader    efficient line reads
  newBufferedWriter(p)  → BufferedWriter    efficient text writes
  newInputStream(p)     → InputStream       read raw bytes (stream)
  newOutputStream(p)    → OutputStream      write raw bytes (stream)
  newByteChannel(p)     → SeekableByteChannel  random access (seek)

  rule of thumb: small text → readAllLines / write
                big or streaming → Buffered* / *Stream / channel
  doodle: ruler L→R: [readAllLines]──[Buffered]──[Stream]──[Channel]
          left=easy/whole-file, right=powerful/streaming
// APPEND vs OVERWRITE — OPEN OPTIONS
APPENDING TO A FILE
  append → newBufferedWriter(path, StandardOpenOption.APPEND)
  if file missing → NoSuchFileException
  fix → add CREATE:
     newBufferedWriter(path, CREATE, APPEND)   // create if absent, then append

StandardOpenOption (key flags)
OptionEffect
APPENDwrite at end of existing file
CREATEcreate if not present (keep if present)
CREATE_NEWcreate, FAIL if already exists
TRUNCATE_EXISTINGwipe contents to 0 then write
WRITEopen for writing
  defaults: write()/newBufferedWriter() w/ NO option
            = CREATE + WRITE + TRUNCATE  → OVERWRITES!
  doodle: file box; APPEND = pen adds at bottom;
          TRUNCATE = eraser clears box first
// ACTIVITY 1 — WORD/LINE/CHAR/PARA COUNT
ACT#1: count Lines / Words / Chars / Paragraphs → output.txt
  read file → loop lines
  lines++       per line read
  words+=       line.split("\\s+").length  (skip empty)
  chars+=       line.length()  (+1 per newline if counting them)
  paragraphs++  on blank-line separator (blank line = para break)
  out format:
     Total Lines Count - N
     Total Words Count - N
     Total Characters Count - N
     Total Paragraphs Count - N
  para def: block of text separated by ≥1 empty line
// CSV RULES (RFC 4180)
CSV = Comma-Separated Values (tabular data as plain text)
  1 line  = 1 record
  fields separated by commas
  field WITH comma  → wrap in "double quotes"
  " INSIDE a value  → write it twice ""  (escape)
    e.g.  She said ""hi""  →  "She said ""hi"""
  doodle: grid table → flatten into rows of a,b,c
          (spreadsheet → text lines)

ACT#2: total price per item
  IN : ITEM,DESCRIPTION,PRICE,QUANTITY
  OUT: ITEM,DESCRIPTION,TOTAL_PRICE
  TOTAL_PRICE = PRICE × QUANTITY
  steps: read lines → skip header → split "," → compute → write new CSV
  naive split(",") breaks on quoted commas — fine for simple data
// READ vs WRITE METHOD PICKER
NeedReadWrite
whole small textreadAllLineswrite(p,lines)
raw bytes (img/zip)readAllByteswrite(p,bytes)
large / line-by-linenewBufferedReadernewBufferedWriter
stream of bytesnewInputStreamnewOutputStream
random accessnewByteChannel
// XNEWS BUILDOUT — PIPELINE
XNEWS = serve curated news by user prefs (topic, language…)
  FLOW:
    prefs.json ──read(File I/O)──> Jackson parse ──> build request
        │
        ▼
    OkHttp GET → NewsAPI ──> JSON response
        │
        ▼
    Jackson deserialize → Article objects → pretty print to user

  doodle: prefs.json → [Jackson] → [OkHttp]──HTTP──>(NewsAPI)
          → [Jackson] → printed articles

  TOOLS:
    File I/O  → read prefs.json
    Jackson   → JSON ⇄ Java objects (parse + deserialize)
    OkHttp    → make REST API call (HTTP GET)
    NewsAPI   → external news source (returns JSON)
  serialize   = Java obj → JSON text
  deserialize = JSON text → Java obj

Full explanations + runnable code: jump to Detailed Notes →

Section II

Detailed Study Notes#

One numbered concept per subsection. + marks anything I added or corrected beyond the core material — enrichment, fixes, and "good to know" extras.

1. Why files exist: persistence#

Files let data outlive a single program run — the difference between memory (temporary) and disk (permanent).

A note-taking app remembers what I typed even after I close it and power off the phone. That only works because the text was written to a file on disk, not just held in RAM. RAM (variables, objects) is wiped the moment the process ends or power is lost; a file on storage stays put until something deletes it. This idea is called persistence.

Everyday file I/O (input/output) is everywhere: a media player reading a video file, a spreadsheet program opening a CSV, an application writing log lines as it runs. + Other common cases: saving game progress, app settings/config files, caching downloaded data, exporting reports, and databases (which are ultimately structured files on disk).

Why it mattersAlmost every real program needs to save or load something. File I/O is the bridge between "data while running" and "data that lasts." Get this mental model first; the API choices follow from it.
Interview angleBe ready to explain the difference between volatile memory (RAM) and non-volatile storage (disk/SSD), and why we serialize data to files or databases for durability.

2. Path and Paths.get — naming a file before touching it#

A Path is just a typed reference to a location; creating one does no disk I/O.

+ Before reading or writing, I describe where the file is using Paths.get("output.txt"), which returns a Path object. This is purely a label — the file need not exist yet, and nothing is opened. The actual reading/writing happens when I hand that Path to a Files method. Think of Path as writing an address on an envelope; Files methods are the postal service that actually delivers.

+ On modern Java you'll also see Path.of("output.txt") — it's the newer equivalent of Paths.get(...) (added in Java 11) and is preferred going forward. Both produce the same kind of object.

Why it mattersSeparating "name a location" from "do I/O" keeps code clean and makes errors precise — a bad path fails differently from a missing file. See it used in every sample → Section III.

3. The Files method family, simple → complex#

java.nio.file.Files is a utility class of static helpers; pick the one matching your data size and access pattern.

These methods sit on a spectrum from "easiest, loads everything" to "most powerful, most control":

MethodWhat it doesReturns
readAllBytesRead every byte of a file at oncebyte[]
readAllLinesRead every line of text at onceList<String>
write (bytes)Write a byte array to a filePath
write (lines)Write lines of text to a filePath
newBufferedReaderOpen file → efficient text readerBufferedReader
newBufferedWriterOpen/create file → efficient text writerBufferedWriter
newInputStreamOpen file → byte input streamInputStream
newOutputStreamOpen/create file → byte output streamOutputStream
newByteChannelOpen/create → seekable channel (random access)SeekableByteChannel

The split that matters: readAll* and the line/byte write overloads load the whole file into memory in one shot — dead simple, perfect for small files. The Buffered* and *Stream/Channel variants process data incrementally, so memory stays low even for huge files.

+ "Buffered" means the reader/writer keeps a small in-memory chunk (a buffer) so it talks to the slow disk in big gulps instead of one character at a time — far fewer system calls, much faster. Analogy: carrying water with a bucket (buffer) instead of one cupped handful per trip.

+ Bytes vs text: use the byte methods (readAllBytes, newInputStream, newOutputStream) for binary files like images, PDFs, or zips; use the text/line methods for human-readable text. + readAllLines defaults to UTF-8 encoding in Java 8+.

Trade-offsWhole-file (readAll*): simplest code, but loads everything into RAM — an OutOfMemoryError waiting to happen on large files. Streaming/Buffered: constant memory and faster for big data, but more code and you must remember to close resources.
Why it mattersChoosing the wrong tool is a real bug source: readAllBytes on a 4 GB log will crash; a buffered loop won't. Interviewers love asking "how would you process a file too big to fit in memory?" — answer: stream it.
Interview anglejava.nio ("new I/O") vs the older java.io: Files + Path is the modern, more convenient API. + NIO also brings non-blocking I/O and channels; for plain file reads, the headline win is concise one-liners like Files.readAllLines.

Runnable demos of each → Code: Files method tour.

4. Appending data & StandardOpenOption#

By default, writing overwrites a file; to add to the end you must explicitly pass StandardOpenOption.APPEND.

To append text instead of replacing it:

Files.newBufferedWriter(Paths.get("output.txt"), StandardOpenOption.APPEND);

But there's a trap: if output.txt doesn't exist yet, this throws NoSuchFileException — because APPEND alone says "open the existing file at its end," and there is no existing file. The fix is to combine it with CREATE:

Files.newBufferedWriter(Paths.get("output.txt"),
        StandardOpenOption.CREATE, StandardOpenOption.APPEND);

Now it creates the file if absent, then appends — safe either way.

+ The full set of common open options worth knowing:

OptionMeaning
WRITEOpen the file for writing.
APPENDBytes go to the end of the file (requires the file to be writable).
CREATECreate the file if it doesn't exist; if it does, just open it.
CREATE_NEWCreate the file, but fail with an exception if it already exists (useful to avoid clobbering).
TRUNCATE_EXISTINGIf the file exists, wipe it to length 0 before writing.

+ Important default: when you call Files.write(...) or newBufferedWriter(...) with no options, Java uses CREATE + WRITE + TRUNCATE_EXISTING — i.e., it creates-or-overwrites. That's why a second write silently erases the first unless you append.

PitfallAPPEND by itself on a missing file → NoSuchFileException. + And APPEND combined with TRUNCATE_EXISTING is contradictory and throws IllegalArgumentException — never pair them.
Why it mattersLogging is the classic append use case: each run adds lines without destroying history. Forgetting APPEND means every run nukes yesterday's logs.

Runnable: → Code: append vs overwrite.

5. BufferedReader & BufferedWriter#

Buffered streams batch disk access through an in-memory buffer, making line-by-line text I/O efficient.

Files.newBufferedReader(path) hands back a BufferedReader you read with readLine() in a loop (returns null at end-of-file). Files.newBufferedWriter(path) gives a BufferedWriter you write with write(...) / newLine(). They're the workhorses for processing text files of any size without loading it all at once.

+ Always wrap them in a try-with-resources block so they close automatically — closing a BufferedWriter also flushes any buffered bytes to disk. Forgetting to close/flush can mean your last lines never actually get written.

PitfallIf you don't close (or flush) a writer, buffered data sitting in memory can be lost — the file looks empty or truncated. try-with-resources solves this for you.
Interview angleWhy "buffered"? Each raw disk/system call is expensive; a buffer turns thousands of tiny writes into a few big ones. Same reason you batch network requests.

Runnable: → Code: BufferedReader/Writer.

6. Activity #1 — count lines, words, characters, paragraphs#

Read a text file, tally four metrics, and write them to output.txt in a fixed format.

Required output format:

Total Lines Count - 10
Total Words Count - 10
Total Characters Count - 10
Total Paragraphs Count - 10

The approach: read the file line by line with a BufferedReader, and for each line update counters.

  • Lines — increment once per line read.
  • Words — split the line on whitespace (line.split("\\s+")) and add the count; + guard against empty/blank lines so they don't count as one word.
  • Characters — add line.length() per line. + Decide explicitly whether newline characters count; the line string from readLine() excludes the \n, so add it manually if your spec counts newlines.
  • Paragraphs — + a paragraph is a block of text separated by one or more blank lines; increment the paragraph count when you transition from a blank line back to a non-blank line (or count non-empty blocks).

Then write all four counters to output.txt with a BufferedWriter.

PitfallOff-by-one on paragraphs is the classic bug: counting every blank line as a paragraph break inflates the total when there are consecutive blank lines. Use a "was the previous line blank?" flag.

Full solution: → Code: FileCount · also Lab Ex.3.

7. CSV — comma-separated values#

CSV stores tabular data as plain text: one record per line, fields separated by commas, with quoting rules for awkward characters.

CSV is how spreadsheets and tables travel as simple text. The rules:

  • One line = one record (one row of the table).
  • Fields are separated by commas.
  • A field that contains a comma must be wrapped in double quotes, so the comma inside isn't mistaken for a separator: "Smith, John",42.
  • A double quote inside a value is written as two double quotes (doubled to escape it): "She said ""hi""" represents She said "hi".

+ These conventions are standardized as RFC 4180. + Real-world CSV also varies: some files use semicolons or tabs as separators (regional locales), and the first line is often a header row of column names.

Value to storeWritten in CSV as
Plain textPlain text
Boston, MA"Boston, MA"
She said "hi""She said ""hi"""
line1⏎line2 (newline)"line1⏎line2" (quoted)
PitfallNaively splitting on "," breaks the moment a field contains a quoted comma — "Boston, MA" becomes two fields. Fine for clean numeric data; for messy real data use a proper CSV parser (e.g., OpenCSV, Apache Commons CSV). +
Why it mattersCSV is the lingua franca of data exchange — exports from Excel, databases, analytics tools. Knowing the quoting rules prevents silent data corruption.

Runnable parsing: → Code: CSV read/compute/write.

8. Activity #2 — total price per item (CSV → CSV)#

Read an input CSV, compute a derived column, and write a reshaped output CSV.

Input format: ITEM,DESCRIPTION,PRICE,QUANTITY
Output format: ITEM,DESCRIPTION,TOTAL_PRICE

For each data row, TOTAL_PRICE = PRICE × QUANTITY, and the QUANTITY column is dropped. Steps: read all lines, skip (or re-emit) the header, split each row on commas, parse PRICE and QUANTITY as numbers, multiply, then write ITEM,DESCRIPTION,TOTAL_PRICE to the new file.

Trade-offsSimple split(",") is fine here because the sample data is clean numbers; + if descriptions could contain commas you'd need quoting-aware parsing (see §7 pitfall).

Full solution: → Code: TotalPrice · also Lab Ex.4.

9. XNews — what I'm building#

XNews is a Java app that fetches and prints news articles tailored to a user's preferences (topic, language, etc.).

It works like searching Google for news on a topic, but as my own program. The end-to-end pipeline:

  1. File handling — read the user's preferences from a JSON file.
  2. Jackson — parse that JSON into Java objects.
  3. OkHttp — make a REST API request (HTTP GET) to NewsAPI using those preferences.
  4. Jackson — deserialize the API's JSON response into Java objects.
  5. Finally, print the articles neatly for the user.

+ Jackson is the standard Java library for converting between JSON and Java objects. Serialize = Java object → JSON text; deserialize = JSON text → Java object. + OkHttp is a popular HTTP client library for making web requests from Java. + REST API = a web service you call over HTTP (usually returning JSON); NewsAPI is one such service that returns news articles as JSON and typically requires a free API key.

Diagram (data flow):

prefs.json ──read (File I/O)──> Jackson parse ──> Java prefs object
                                                  │
                                                  ▼ build request
                                            OkHttp HTTP GET ───> (NewsAPI)
                                                  │
                                       JSON response <──────────┘
                                                  │
                                       Jackson deserialize
                                                  │
                                                  ▼
                                       List<Article> → printed to user
Why it mattersThis is a complete, realistic data pipeline: local file → parse → network call → parse → present. The same shape (read config, call API, render results) underlies a huge fraction of real apps.
Interview angleKnow the JSON ⇄ object vocabulary (serialize/deserialize/marshalling), what a REST GET is, and why we use a client library (OkHttp) instead of hand-writing sockets. + Also: never hard-code API keys — load them from config/env.

Runnable skeleton: → Code: XNews pipeline.


★ Key Takeaways#

  • Files persist data beyond a program's lifetime — that's the whole point of file I/O.
  • Path just names a location; Files methods do the actual work.
  • Match the method to the data: readAll*/write for small whole files, Buffered*/*Stream/Channel for large or streaming data.
  • Writing overwrites by default. Use StandardOpenOption.APPEND to add, and pair it with CREATE to avoid NoSuchFileException on a missing file.
  • Always close writers (try-with-resources) so buffers flush.
  • CSV = one record per line, comma-separated; quote fields with commas, double the inner quotes (RFC 4180).
  • XNews pipeline: read JSON prefs (File I/O) → Jackson parse → OkHttp call to NewsAPI → Jackson deserialize → print. Serialize = obj→JSON, deserialize = JSON→obj.
Section III

Code Samples — runnable#

Standard JDK only (target Java 17, an LTS). Each block has a filename, run command, inline comments, expected output, and a "why" note. Single-file programs run directly with java File.java (source-launch mode, JDK 11+).

III-1 Files method tour ↔ §3#

FilesTour.javarun: java FilesTour.java
import java.nio.file.*;
import java.util.List;
import java.io.*;

public class FilesTour {
  public static void main(String[] args) throws IOException {
    Path p = Paths.get("sample.txt");          // names a file; no I/O yet

    // --- WRITE (whole file). Default = CREATE+WRITE+TRUNCATE (overwrites) ---
    Files.write(p, List.of("line one", "line two")); // writes 2 lines

    // --- readAllLines: whole file into a List<String> (UTF-8) ---
    List<String> lines = Files.readAllLines(p);
    System.out.println("lines = " + lines);

    // --- readAllBytes: whole file as raw bytes (good for binary) ---
    byte[] bytes = Files.readAllBytes(p);
    System.out.println("byte count = " + bytes.length);

    // --- BufferedReader: efficient line-by-line for any size ---
    try (BufferedReader br = Files.newBufferedReader(p)) {
            String l;
            while ((l = br.readLine()) != null)   // readLine()==null at EOF
            System.out.println("read: " + l);
    }
  }
}
EXPECTED OUTPUTlines = [line one, line two] byte count = 18 read: line one read: line two

Why: Files.write(p, List.of(...)) with no options overwrites because the default open options are CREATE+WRITE+TRUNCATE_EXISTING. byte count = 18 = "line one"(8) + "line two"(8) + 2 newlines. readLine() strips the newline, which is why each printed line has none.

III-2 Append vs overwrite ↔ §4#

AppendDemo.javarun: java AppendDemo.java
import java.nio.file.*;
import static java.nio.file.StandardOpenOption.*;
import java.io.*;

public class AppendDemo {
  public static void main(String[] args) throws IOException {
    Path p = Paths.get("log.txt");

    // CREATE + APPEND: makes file if missing, then adds to the END.
    // APPEND alone on a missing file would throw NoSuchFileException.
    try (BufferedWriter w = Files.newBufferedWriter(p, CREATE, APPEND)) {
            w.write("run at " + System.currentTimeMillis());
            w.newLine();                       // platform line separator
    }                                     // close() flushes buffer to disk

    // Run this program twice: the file keeps BOTH lines (proof of append).
    Files.readAllLines(p).forEach(System.out::println);
  }
}
EXPECTED OUTPUT (after running twice)run at 1750000000001 run at 1750000000875

Why: Without APPEND, the second run's default TRUNCATE would erase the first line. CREATE prevents the missing-file crash. The try-with-resources close() flushes — drop it and buffered text may never hit disk.

III-3 BufferedReader / BufferedWriter ↔ §5#

BufferedCopy.javarun: java BufferedCopy.java
import java.nio.file.*;
import java.io.*;

public class BufferedCopy {
  public static void main(String[] args) throws IOException {
    Path in  = Paths.get("in.txt");
    Path out = Paths.get("out.txt");
    Files.write(in, java.util.List.of("apple", "banana", "cherry"));

    // Stream large files line-by-line without loading the whole thing.
    try (BufferedReader r = Files.newBufferedReader(in);
        BufferedWriter w = Files.newBufferedWriter(out)) {
            String line; int n = 0;
            while ((line = r.readLine()) != null) {
                w.write((++n) + ": " + line.toUpperCase());
                w.newLine();
            }
    }
    Files.readAllLines(out).forEach(System.out::println);
  }
}
EXPECTED OUTPUT1: APPLE 2: BANANA 3: CHERRY

Why: Both resources are declared in one try-with-resources, so both close (and the writer flushes) even if an exception fires mid-loop. Memory use stays flat regardless of file size because only one line is held at a time.

III-4 Activity #1 — FileCount ↔ §6#

FileCount.javarun: java FileCount.java
import java.nio.file.*;
import java.io.*;

public class FileCount {
  public static void main(String[] args) throws IOException {
    Path in = Paths.get("input.txt");
    // seed an input file: 2 paragraphs separated by a blank line
    Files.write(in, java.util.List.of("Hello world here", "second line", "", "new paragraph now"));

    long lines = 0, words = 0, chars = 0, paragraphs = 0;
    boolean prevBlank = true;   // start "blank" so first text block counts

    try (BufferedReader br = Files.newBufferedReader(in)) {
            String line;
            while ((line = br.readLine()) != null) {
                lines++;                                    // 1 per line
                chars += line.length() + 1;                   // +1 counts the newline
                boolean blank = line.trim().isEmpty();
                if (!blank) {
                    words += line.trim().split("\\s+").length; // split on whitespace
                    if (prevBlank) paragraphs++;               // blank→text = new para
                    }
                    prevBlank = blank;
            }
    }

    try (BufferedWriter w = Files.newBufferedWriter(Paths.get("output.txt"))) {
            w.write("Total Lines Count - " + lines);      w.newLine();
            w.write("Total Words Count - " + words);      w.newLine();
            w.write("Total Characters Count - " + chars); w.newLine();
            w.write("Total Paragraphs Count - " + paragraphs);
    }
    Files.readAllLines(Paths.get("output.txt")).forEach(System.out::println);
  }
}
EXPECTED OUTPUTTotal Lines Count - 4 Total Words Count - 8 Total Characters Count - 48 Total Paragraphs Count - 2

Why: Words = 3 (line1) + 2 (line2) + 3 (line4) = 8; the blank line adds none. Paragraphs = 2 because there are two text blocks split by the blank line — the prevBlank flag is what prevents counting consecutive blanks as multiple breaks. Chars includes a +1 per line for the newline (16+11+0+17 text chars + 4 newlines... here counted as length+1 each = 48); if your spec excludes newlines, drop the + 1.

III-5 CSV read / compute / write ↔ §7#

CsvDemo.javarun: java CsvDemo.java
import java.nio.file.*;
import java.util.*;

public class CsvDemo {
  public static void main(String[] args) throws Exception {
    Path in = Paths.get("items.csv");
    Files.write(in, List.of("ITEM,DESCRIPTION,PRICE,QUANTITY",   // header row
        "Pen,Blue ink,10,3", "Notebook,A5 ruled,50,2"));

    List<String> lines = Files.readAllLines(in);
    List<String> outRows = new ArrayList<>();
    outRows.add("ITEM,DESCRIPTION,TOTAL_PRICE");     // new header

    for (int i = 1; i < lines.size(); i++) {     // i=1 skips header
            String[] f = lines.get(i).split(",");    // naive split (clean data)
            double total = Double.parseDouble(f[2]) * Double.parseDouble(f[3]);
            outRows.add(f[0] + "," + f[1] + "," + total);
    }
    Files.write(Paths.get("out.csv"), outRows);
    outRows.forEach(System.out::println);
  }
}
EXPECTED OUTPUTITEM,DESCRIPTION,TOTAL_PRICE Pen,Blue ink,30.0 Notebook,A5 ruled,100.0

Why: split(",") works only because no field here contains a comma. Double.parseDouble gives 30.0 (note the trailing .0) — use int parsing or formatting if you want whole numbers. This is the core of the second exercise; see the fuller version below.

III-6 Activity #2 — TotalPrice ↔ §8#

TotalPrice.javarun: java TotalPrice.java input.csv output.csv
import java.nio.file.*;
import java.util.*;

public class TotalPrice {
  public static void main(String[] args) throws Exception {
    Path in  = Paths.get(args.length > 0 ? args[0] : "input.csv");
    Path out = Paths.get(args.length > 1 ? args[1] : "output.csv");

    if (!Files.exists(in))                       // seed demo data if absent
        Files.write(in, List.of("ITEM,DESCRIPTION,PRICE,QUANTITY", "Pen,Blue ink,10,3", "Mug,Ceramic,120,2"));

    List<String> rows = new ArrayList<>();
    rows.add("ITEM,DESCRIPTION,TOTAL_PRICE");
    List<String> src = Files.readAllLines(in);

    for (int i = 1; i < src.size(); i++) {
            String[] c = src.get(i).split(",");
            int price = Integer.parseInt(c[2].trim());
            int qty   = Integer.parseInt(c[3].trim());
            rows.add(c[0] + "," + c[1] + "," + (price * qty)); // derived col
    }
    Files.write(out, rows);
    System.out.println("Wrote " + (rows.size()-1) + " item rows to " + out);
    rows.forEach(System.out::println);
  }
}
EXPECTED OUTPUTWrote 2 item rows to output.csv ITEM,DESCRIPTION,TOTAL_PRICE Pen,Blue ink,30 Mug,Ceramic,240

Why: Parsing as int (not double) keeps prices whole (30, not 30.0). .trim() guards against stray spaces around numbers. Accepting file names via args makes it reusable from the command line.

III-7 XNews pipeline (skeleton) ↔ §9#

XNews uses third-party libraries (Jackson, OkHttp) which aren't in the standard library, so the network/JSON parts can't run with bare java File.java. + Below is (a) a pure-JDK version that proves the same pipeline shape with no dependencies, and (b) the real Jackson/OkHttp shape annotated as reference.

XNewsLite.java — pure JDK, runnablerun: java XNewsLite.java
import java.nio.file.*;
import java.net.http.*;        // JDK 11+ HttpClient — no OkHttp needed
import java.net.URI;
import java.util.List;

public class XNewsLite {
  public static void main(String[] args) throws Exception {
    // 1) FILE I/O: read user preferences from a JSON-ish file
    Path prefs = Paths.get("prefs.json");
    if (!Files.exists(prefs)) Files.write(prefs, List.of("{ \"topic\": \"java\", \"language\": \"en\" }"));
    String json = Files.readString(prefs);          // whole file as String
    System.out.println("prefs = " + json.trim());

    // 2) PARSE (real XNews uses Jackson; here a tiny manual extract)
    String topic = json.replaceAll("(?s).*\"topic\"\\s*:\\s*\"([^\"]+)\".*", "$1");

    // 3) HTTP GET to an API (real XNews uses OkHttp + NewsAPI)
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest req = HttpRequest.newBuilder().uri(URI.create("https://example.com/?topic=" + topic))
        .GET().build();
    System.out.println("would GET news for topic = " + topic);
    // (omitting the live send so this runs offline)

    // 4) DESERIALIZE response → 5) print articles (shown in full version)
    System.out.println("pipeline OK: file -> parse -> request -> (parse) -> print");
  }
}
EXPECTED OUTPUTprefs = { "topic": "java", "language": "en" } would GET news for topic = java pipeline OK: file -> parse -> request -> (parse) -> print

Why: This mirrors XNews's 5 stages using only the JDK (Files.readString, java.net.http.HttpClient) so it actually runs. The regex "parse" is a stand-in for Jackson — fine for a demo, never for real JSON.

XNews.java — real shape (Jackson + OkHttp), referenceneeds: Jackson + OkHttp on classpath (Maven/Gradle)
// Dependencies (Maven): jackson-databind, okhttp
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import java.nio.file.*;

public class XNews {
  record Prefs(String topic, String language) {}     // maps prefs.json
  record Article(String title, String url) {}         // maps API JSON

  public static void main(String[] a) throws Exception {
    ObjectMapper m = new ObjectMapper();

    // 1) File I/O + 2) Jackson: JSON file -> Prefs object (deserialize)
    Prefs prefs = m.readValue(Files.readAllBytes(Paths.get("prefs.json")), Prefs.class);

    // 3) OkHttp: REST GET to NewsAPI (key from env, never hard-coded) [+]
    String key = System.getenv("NEWS_API_KEY");
    OkHttpClient http = new OkHttpClient();
    Request req = new Request.Builder().url("https://newsapi.org/v2/everything?q=" + prefs.topic()
        + "&language=" + prefs.language() + "&apiKey=" + key).build();

    try (Response resp = http.newCall(req).execute()) {
            String body = resp.body().string();          // raw JSON
            // 4) Jackson: deserialize the "articles" array
            var root = m.readTree(body);
            for (var node : root.get("articles")) {     // 5) print neatly
            System.out.println("• " + node.get("title").asText() + "\n  " + node.get("url").asText());
            }
    }
  }
}
EXPECTED OUTPUT (with a valid key)• Java 21 features you should know https://example.news/java-21 • Spring Boot 3 release notes https://example.news/spring-boot-3

Why: ObjectMapper.readValue(bytes, Prefs.class) is Jackson deserialization (JSON→object). The OkHttp Response is in try-with-resources because it holds a network connection that must close. The API key comes from an environment variable — + hard-coding secrets in source is a real security mistake.

Section IV

Hands-On Lab — practice#

Six graded exercises. Predict the output before you run, then verify. Solutions are collapsed — write yours first.

★ One-time Setup
# 1. Check Java is installed (need JDK 11+ for `java File.java`; 17 LTS ideal)
java -version
javac -version

# 2. Make a working folder
mkdir -p ~/workspace/file-handling-lab
cd ~/workspace/file-handling-lab

# 3. Create a source file, e.g. Ex1.java, paste your code, then:
java Ex1.java            # single-file source-launch, no separate compile

# --- OR the classic two-step (any JDK) ---
javac Ex1.java           # compile -> Ex1.class
java Ex1                 # run (note: no .java / .class extension)
Warm-up · 1

Write then read three lines#

Goal: Write three lines to hello.txt using Files.write, then read them back with Files.readAllLines and print them.

  1. Build a Path for hello.txt.
  2. Write List.of("a","b","c") with Files.write.
  3. Read with readAllLines and print each line.
Predict then verify: If you run the program twice, how many lines are in the file the second time — 3 or 6? Why?
Solution
Ex1.javarun: java Ex1.java
import java.nio.file.*; import java.util.List;
public class Ex1 {
  public static void main(String[] a) throws Exception {
    Path p = Paths.get("hello.txt");
    Files.write(p, List.of("a", "b", "c"));   // overwrites each run
    Files.readAllLines(p).forEach(System.out::println);
  }
}
OUTPUTa b c

Answer: Still 3 lines. Files.write with no options defaults to TRUNCATE_EXISTING, so each run wipes and rewrites — it does not accumulate.

Warm-up · 2

Append a timestamp every run#

Goal: Append a line to runs.txt on each execution so the file grows. Make it not crash on the first run when the file is missing.

  1. Open a BufferedWriter with the correct open options.
  2. Write the current time, then a newline.
  3. Run it 3 times; confirm 3 lines.
Predict then verify: What exception do you get if you use APPEND alone and the file doesn't exist yet? Which single option fixes it?
Solution
Ex2.javarun: java Ex2.java (×3)
import java.nio.file.*; import static java.nio.file.StandardOpenOption.*;
import java.io.*;
public class Ex2 {
  public static void main(String[] a) throws Exception {
    try (BufferedWriter w = Files.newBufferedWriter(Paths.get("runs.txt"), CREATE, APPEND)) {
            w.write("run @ " + java.time.LocalTime.now());
            w.newLine();
    }
    Files.readAllLines(Paths.get("runs.txt")).forEach(System.out::println);
  }
}
OUTPUT (after 3 runs)run @ 21:50:01.112 run @ 21:50:02.349 run @ 21:50:03.880

Answer: APPEND alone → NoSuchFileException on a missing file. Adding CREATE fixes it.

Core · 3

FileCount (Exercise 1)#

Goal: Count lines, words, characters, and paragraphs of an input file and write the four totals to output.txt in the required format.

  1. Seed an input.txt with at least 2 paragraphs (blank line between).
  2. Loop lines; update the four counters (use a prevBlank flag for paragraphs).
  3. Write the four Total ... Count - N lines to output.txt.
Predict then verify: For a file with two non-empty paragraphs separated by two blank lines, does your paragraph count stay at 2? (It should — that's the whole point of the flag.)
Solution

Use the full FileCount.java from Section III-4. The key idea: only bump paragraphs on a blank→non-blank transition, so consecutive blank lines never over-count.

Self-test: With paragraphs of 3 and 2 words split by blank line(s), you should see Total Words Count - 5 and Total Paragraphs Count - 2 regardless of how many blank lines separate them.

Core · 4

TotalPrice (Exercise 2)#

Goal: Read ITEM,DESCRIPTION,PRICE,QUANTITY, compute TOTAL_PRICE = PRICE×QUANTITY, write ITEM,DESCRIPTION,TOTAL_PRICE to a new CSV.

  1. Read all lines; keep line 0 logic separate (it's the header).
  2. Write a new header ITEM,DESCRIPTION,TOTAL_PRICE.
  3. For each data row: split(","), parse price & qty, multiply, append.
  4. Write the result CSV.
Predict then verify: If a price is 10 and quantity 3, do you want output 30 or 30.0? Which numeric type gives which?
Solution

Use TotalPrice.java (Section III-6). Parse with Integer.parseInt for clean 30; use Double.parseDouble if prices can be decimals (you'll get 30.0).

Self-test: Output header must be exactly ITEM,DESCRIPTION,TOTAL_PRICE (QUANTITY column dropped), and row count = input rows − 1 header.

Challenge · 5

Quote-aware CSV split#

Goal: Parse a CSV row where a description may contain a quoted comma, e.g. Mug,"Big, blue",120,2, without breaking the field on the inner comma.

  1. Don't use naive split(",") — it splits inside the quotes.
  2. Walk the string char-by-char, toggling an inQuotes flag on "; only split on commas when !inQuotes.
  3. Verify the description stays intact as one field.
Predict then verify: With naive split, how many fields does Mug,"Big, blue",120,2 produce (wrongly)? How many should it be?
Solution
Ex5.javarun: java Ex5.java
import java.util.*;
public class Ex5 {
  static List<String> parse(String row) {
    List<String> out = new ArrayList<>();
    StringBuilder cur = new StringBuilder();
    boolean inQ = false;
    for (char ch : row.toCharArray()) {
            if (ch == '"') inQ = !inQ;              // toggle quote state
            else if (ch == ',' && !inQ) {            // split only outside quotes
                out.add(cur.toString()); cur.setLength(0);
            } else cur.append(ch);
    }
    out.add(cur.toString());
    return out;
  }
  public static void main(String[] a) {
    System.out.println(parse("Mug,\"Big, blue\",120,2"));
  }
}
OUTPUT[Mug, Big, blue, 120, 2]

Answer: Naive split yields 5 fields (wrong); correct is 4. The parser keeps Big, blue as one field. Note it doesn't yet un-double ""; production code should also collapse doubled quotes and ideally use a real CSV library.

Challenge · 6

Mini XNews pipeline (offline)#

Goal: Reproduce the XNews shape with only the JDK: read a prefs.json file, extract the topic, and print what request you'd make — proving the file→parse→request flow end to end.

  1. Create prefs.json with a topic field if missing.
  2. Read it with Files.readString.
  3. Extract topic (regex is fine for the drill; Jackson in real code).
  4. Print the URL you'd GET.
Predict then verify: Which two real libraries would replace your manual JSON parse and your HttpClient in the production XNews — and which one does both serialize and deserialize?
Solution

Use XNewsLite.java (Section III-7). It reads prefs.json, pulls out the topic, and prints the request it would make — all offline with the JDK.

Answer: Jackson replaces manual JSON parsing (and it handles both serialize and deserialize), and OkHttp replaces the HTTP client for the NewsAPI call.

✓ Self-check rubric — how to know you've got it#

  • Method picker: Given a task ("process a 5 GB log", "read a small config", "save an image"), I can name the right Files method without looking.
  • Append vs overwrite: I can explain why a re-run erased my file, and fix it with CREATE, APPEND — and I know why APPEND alone can throw.
  • Resource safety: I always wrap readers/writers in try-with-resources and can say why (auto-close + flush).
  • Counting logic: My paragraph count survives multiple consecutive blank lines (the prevBlank flag works).
  • CSV rules: I can hand-write the CSV encoding of She said "hi", ok? correctly (quotes doubled, field wrapped).
  • XNews flow: I can draw the 5-stage pipeline from memory and define serialize vs deserialize, naming Jackson and OkHttp's roles.