Files and IO Enhancements (Java 11)
The Problem: File IO Boilerplate
Reading a file’s content in Java 8 required several lines of boilerplate, even for simple tasks:
// Java 8 — read entire file as String
String content;
try (var reader = new BufferedReader(new FileReader("config.txt"))) {
content = reader.lines().collect(Collectors.joining("\n"));
}
// Java 8 — write a String to a file
try (var writer = new BufferedWriter(new FileWriter("output.txt"))) {
writer.write(content);
}
Java 11 reduced this to one-liners.
Files.readString(Path)
Reads the entire file as a String in a single call:
// Java 11
String content = Files.readString(Path.of("config.txt"));
With explicit charset:
String content = Files.readString(Path.of("report.txt"), StandardCharsets.UTF_8);
String latin = Files.readString(Path.of("legacy.txt"), StandardCharsets.ISO_8859_1);
Default charset is StandardCharsets.UTF_8 when called without a charset argument.
Under the hood
Files.readString() reads all bytes via Files.readAllBytes() and then decodes them. It is functionally equivalent to:
new String(Files.readAllBytes(path), charset)
but is more expressive and handles charset argument consistently.
Practical examples
// Read and parse JSON
String json = Files.readString(Path.of("config.json"));
var config = objectMapper.readValue(json, Config.class);
// Read and process a template
String template = Files.readString(Path.of("email-template.html"));
String rendered = template
.replace("{{name}}", user.getName())
.replace("{{link}}", resetLink);
// Compare file contents
String expected = Files.readString(Path.of("expected.txt"));
String actual = Files.readString(Path.of("actual.txt"));
assertEquals(expected, actual);
Files.writeString(Path, CharSequence)
Writes a CharSequence (String, StringBuilder, etc.) to a file in a single call:
// Java 11
Files.writeString(Path.of("output.txt"), "Hello, Java 11!");
With explicit charset:
Files.writeString(Path.of("output.txt"), content, StandardCharsets.UTF_8);
With OpenOption flags:
// Append to existing file
Files.writeString(Path.of("log.txt"), newLine + "\n", StandardOpenOption.APPEND);
// Create or truncate
Files.writeString(Path.of("report.txt"), reportContent, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
Practical examples
// Write JSON output
var report = generateReport();
Files.writeString(Path.of("report.json"), objectMapper.writeValueAsString(report));
// Dump test fixture
Files.writeString(Path.of("src/test/resources/fixture.txt"), expectedOutput);
// Write config template
String configTemplate = """
server.port=8080
server.host=localhost
database.url=jdbc:postgresql://localhost/mydb
""";
Files.writeString(Path.of("application.properties"), configTemplate);
Files.mismatch(Path, Path)
Finds the position of the first mismatching byte between two files, or returns -1 if the files are identical:
long diff = Files.mismatch(Path.of("file1.txt"), Path.of("file2.txt"));
if (diff == -1) {
System.out.println("Files are identical");
} else {
System.out.println("First difference at byte: " + diff);
}
This is useful for:
- Test assertions comparing output files against expected files
- File deduplication checks
- Detecting partial writes (comparing a written file against the source)
// Test: verify generated file matches expected
@Test
void outputMatchesExpected() throws IOException {
generator.run(outputPath);
long mismatch = Files.mismatch(expectedPath, outputPath);
assertEquals(-1L, mismatch, "Output differs from expected at byte " + mismatch);
}
Path.of() — Static Factory
Java 11 added Path.of(String, String...) as a cleaner alternative to Paths.get():
// Java 8/9/10
Path config = Paths.get("src", "main", "resources", "config.yaml");
// Java 11
Path config = Path.of("src", "main", "resources", "config.yaml");
Path home = Path.of(System.getenv("HOME"));
Path abs = Path.of("/etc", "hosts");
Path.of() and Paths.get() are functionally identical — Path.of() is simply the more natural home for a factory method on the Path interface itself.
InputStream.readAllBytes()
InputStream.readAllBytes() was added in Java 9. It reads all remaining bytes from the stream into a byte array:
// Java 8 — manual loop or IOUtils dependency
byte[] data;
try (var in = socket.getInputStream()) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
int n;
while ((n = in.read(buf)) != -1) {
baos.write(buf, 0, n);
}
data = baos.toByteArray();
}
// Java 9+
byte[] data;
try (var in = socket.getInputStream()) {
data = in.readAllBytes();
}
Combine with Files.readString() or use directly when you need raw bytes:
// Read a resource from the classpath
try (var in = getClass().getResourceAsStream("/schema.sql")) {
byte[] schemaBytes = in.readAllBytes();
String schema = new String(schemaBytes, StandardCharsets.UTF_8);
jdbcTemplate.execute(schema);
}
InputStream.transferTo(OutputStream)
Copies all bytes from an InputStream to an OutputStream. Added in Java 9.
// Java 8 — manual copy loop or IOUtils
byte[] buf = new byte[8192];
int n;
while ((n = in.read(buf)) >= 0) {
out.write(buf, 0, n);
}
// Java 9+
try (var in = request.getInputStream();
var out = Files.newOutputStream(tempFile)) {
in.transferTo(out);
}
Practical uses
// Proxy: pipe HTTP request body to storage
try (var in = httpRequest.getInputStream();
var out = storageService.openOutputStream(blobKey)) {
long bytes = in.transferTo(out);
log.info("Stored {} bytes", bytes);
}
// Copy classpath resource to a temp file
Path tempFile = Files.createTempFile("schema", ".sql");
try (var in = getClass().getResourceAsStream("/db/schema.sql");
var out = Files.newOutputStream(tempFile)) {
in.transferTo(out);
}
Reader.transferTo(Writer)
The same pattern for character streams. Added in Java 10.
try (var reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
var writer = new FileWriter(outputFile, StandardCharsets.UTF_8)) {
reader.transferTo(writer);
}
Complete Example: File Diff Tool
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;
public class FileDiff {
public static void main(String[] args) throws IOException {
if (args.length != 2) {
System.err.println("Usage: FileDiff <file1> <file2>");
System.exit(1);
}
var path1 = Path.of(args[0]);
var path2 = Path.of(args[1]);
// Quick check: are files identical?
if (Files.mismatch(path1, path2) == -1) {
System.out.println("Files are identical.");
return;
}
// Read both files as lines
var lines1 = Files.readString(path1).lines().collect(Collectors.toList());
var lines2 = Files.readString(path2).lines().collect(Collectors.toList());
// Print line-by-line diff
int max = Math.max(lines1.size(), lines2.size());
for (int i = 0; i < max; i++) {
var l1 = i < lines1.size() ? lines1.get(i) : "<missing>";
var l2 = i < lines2.size() ? lines2.get(i) : "<missing>";
if (!l1.equals(l2)) {
System.out.printf("Line %d:%n < %s%n > %s%n", i + 1, l1, l2);
}
}
}
}
What’s Next
Next: HTTP Client API (JEP 321): HTTP/2, Async, and Authentication