Part 8 of 16

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