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.
Connect the contracts
Compare the boundary explained in I/O failures with the assumptions made by this program.
Compare the boundary explained in Traversal costs with the assumptions made by this program.
Continue with ownership and failure checks
Continue with Java Files.walk: close the stream that owns directory handles.
Continue with checked upload and readiness
Continue with Spring file uploads: discard the supplied filename before writing.
Related contract checks
Continue with Java String.split: preserve empty trailing fields in a record, Java Files.move: atomic publication is a filesystem contract.
Related node and runtime checks
Continue with Java Path.normalize: reject lexical escapes without promising symlink safety.
More I/O boundary cases
Continue with Java bounded line reader: reject oversized records without buffering the file, Java InputStream.available is not the remaining stream length.
Continue with: Java walkFileTree: visit failures without hiding incomplete scans, Java DirectoryStream: close iteration and reject silent truncation.
Continue with: Java FileChannel.write: finish the buffer before reporting success, Java positioned FileChannel.read: detect a short record without moving the cursor, Java AsynchronousFileChannel: own the buffer through completion.
Continue with: Java DOM parsing: deny external XML access at the factory, Java GZIPInputStream: cap bytes after decompression.
