Java File vs Path

2017-09-02 java

I’ve been using java.io.File and java.io.File*Stream since Java 1.1, a long time ago. Java 7 introduced a new file API named NIO2 containing, among others, the java.nio.file.Path and java.nio.file.Files classes. It took me a while to lose my habits and embrace the new API.

Spoiler: The most funny part of this article is at the end!

Quick comparison

java.io.File (class) java.nio.file.Path (interface)

file = new File("path/to/file.txt")

path = Paths.get("path/to/file.txt")

file = new File(parentFile, "file.txt")

path = parentPath.resolve("file.txt")

file.getFileName()

path.getFileName().toString()

file.getParentFile()

path.getParent()

file.mkdirs()

Files.createDirectories(path)

file.length()

Files.size(path)

file.exists()

Files.exists(path)

file.delete()

Files.delete(path)

new FileOutputStream(file)

Files.newOutputStream(path)

new FileInputStream(file)

Files.newInputStream(path)

file.listFiles(filter)

Files.list(path) .filter(filter) .collect(toList())

Some additional notes:

  • Path throws IOException more often than File, and rarely return a boolean to tell if something was done (mkdirs(), delete())

  • File is more object oriented than Path: I regret that size(), exists()…​ methods are not on the Path interface. This is probably due to the fact that this API was added in Java 7, but default methods on interfaces were added later in Java 8.

  • Path based InputStream/`OutputStream`s are less expensive from a GC point view. Thanks @kittster for mentionning this article from Cloudbees.

One liners

java.nio.file.Files allows to read, write, copy files in a single line:

Files.write(Paths.get("image.png"), bytes); (1)

List<String> lines = Files.readAllLines(Paths.get("letter.txt"), StandardCharsets.UTF_8); (2)

Files.lines(Paths.get("letter.txt"), StandardCharsets.UTF_8)
                .forEach(System.out::println);
1 Write a binary file
2 Read a text file

This nearly makes Guava IO and Commons IO useless. I regret that there isn’t any method out of the box to read/write a whole file as a single string.

Many APIs (JAXB, Jackson to name a few) don’t use Path`s to read/write files, the workaround is usually use an `InputStream or an OutputStream.

try(InputStream inputStream = Files.newInputStream(path)) {
  Thing thing = (Thing) unmarshaller.unmarshal(inputStream);
}

Multiple file systems

When the File is only for local files, Path can also be used to access remote files. A Path is associated to a FileSystem.

To create a new Path instances, there is not constructor (Path is interface), we need to call a factory method. The above 2 lines are the same:

path = Paths.get("path/to/file.txt");
path = FileSystems.getDefault().getPath("path/to/file.txt");

As the default file system is the local one, you get a path to a local file. Depending on the underlying file system, you’ll get a different implementation: sun.nio.fs.UnixPath, sun.nio.fs.WindowsPath…​

With this trick in mind, we can read the content of a Zip file, as if we had extracted it:

URI zipUri = new URI("jar:file:/path/to/archive.zip")
try(FileSystem zipFS = FileSystems.newFileSystem(zipUri, emptyMap())) { (1)
    Path zipPath = zipFS.getPath("/archive") (2)
        Files.list(zipPath)
                .map(Path::toString)
                .forEach(System.out::println);
}
1 "Mount" the Zip file as a file system
2 zipPath is of type com.sun.nio.zipfs.ZipPath

You can even plug additional file systems: ZIP, SFTP, SMB, WebDAV, SSH/SCP, Amazon S3, In memory, HDFS, …​ This almost means you can read a remote file as if it were local.