Java NIO file APIs read and write data through Path values while letting the caller choose encoding, buffering, and resource lifetime.
Java file I/O: UTF-8, streaming reads, and path ownership
A path is not an open file
Constructing a Path identifies a filesystem location. Opening a reader performs I/O and can fail for missing files, permissions, resource limits, or concurrent changes. Treat those failures as part of the boundary’s contract.
The program creates its own temporary file, writes a small UTF-8 batch, reads it with a BufferedReader, and removes the file in finally. It does not touch an existing user-selected file. The contents use a known numeric format so the parsing rule is explicit.
An explicit charset prevents platform defaults from changing interpretation across machines. A writer and reader must agree on the format as well as the encoding. Lines with commas, quoted fields, or embedded newlines need a parser for that format rather than an improvised split.
Keep the input size bounded
readAllLines materialises the entire file. A buffered line loop can process one line at a time, but it still needs a maximum line length or bounded input policy for hostile files. Streaming does not turn unlimited input into a safe workload.
Here each accepted line holds an invoice number, and the loop counts records. A real importer should specify whether one invalid line aborts the batch or produces a per-line rejection report.
Do not let an untrusted filename directly select arbitrary storage paths. Directory containment, symlink handling, and the time between validation and opening all matter. This lesson uses a path created by the process to keep those issues outside the demonstration.
Working program
import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
public class Utf8InvoiceBatch {
public static void main(String[] args) throws Exception {
Path batch = Files.createTempFile("aitrove-invoices-", ".txt");
try {
Files.write(batch, Arrays.asList("1041", "1042", "1043"), StandardCharsets.UTF_8);
int records = 0;
try (BufferedReader reader = Files.newBufferedReader(batch, StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
Integer.parseInt(line);
records++;
}
}
System.out.println("records=" + records);
} finally {
Files.deleteIfExists(batch);
}
}
}Output
records=3Cost and design choices
The read visits B input characters, giving O(B) read and parse work for this bounded numeric format. It retains one current line and reader buffering rather than all records; working space depends on the longest line and buffer size.
This example writes an in-memory list first, so its fixture preparation is not a streaming writer for an enormous batch. The read pattern remains useful when the file already exists.
File deletion in a finally block can itself fail and mask a previous error. A production cleanup boundary should preserve the primary failure and report a failed deletion separately. Resource closing provides suppression semantics for closeable resources.
Common Mistakes
- Do not rely on the default charset for a defined text format.
- Do not load an unbounded file into memory without a size policy.
- Do not swallow I/O errors and report an empty successful batch.
