Why Is Java Path Class Better?

2021 02 02 head

Java NIO New Input Output is an alternative IO API for Java, meaning alternative to the standard Java IO and Java Networking APIs.

Java NIO offers a different IO programming model than the traditional IO APIs.

One major feature of interest is the support of alternate file systems. An in-memory file system is ideal for test file related operations in unit and integration tests.

Sometimes NIO is claimed to mean Non-blocking IO. However, this is not what NIO meant originally. Also, parts of the NIO APIs are actually blocking - e.g., the file APIs - so the label "Non-blocking" would be slightly misleading.

Unit Tests

The Jimfs library implements an in-memory file system. You can find other implementations of in-memory file systems if you want to use another library.

The approach is ideal to write unit tests working with files. You cannot assume to have access to a well-defined file system in a generic continuous integration pipeline. Unit tests using an in-memory file system can be deployed in a continuous integration pipeline.

When you look at the implementation of Path.of or Paths.get, you will see:

public static Path of(String first, String... more) {
    return FileSystems.getDefault().getPath(first, more);
}

So, while this method and similar ones are very convenient, using them will imply you want to access your default file system. The one your JVM is running on, not your in-memory file system.

Hence, when wanting to make sure your code works against in-memory file systems, you must make sure to never call these helper methods. Instead, you should always use the FileSystem or a Path instance as an anchor.

Depending on your project and your libraries, this is quite a challenge to pull off [1].

Paths

Alternative file system operations require all methods to use path objects instead of file objects. File objects contain the name of the accessed file but lose all references to the underlying file system. All operations using file objects implicitly use the default file system.

Path objects encapsulate the file and the file system. Operations are aware of the underlying file system and behave as expected.

FileSystem fs;  (1)

Files.createDirectory(fs.getPath(ORGANIZATION, "crm/"));  (2)
Files.copy("myFile", fs.getPath(ORGANIZATION, "crm/")), StandardCopyOption.REPLACE_EXISTING);
1 Define a file system
2 Work with the file system to create paths and avoid using helper methods using the default file system

Consider Java java.io.File class as a legacy abstraction. Avoid using it and moving over to the Java path abstraction.

Remember java.nio.Path class has a reference to a file system, the java.io.File class only uses the default file system.

The Javadoc definition states that a file is

  • An abstract representation of file and directory path names.

  • User interfaces and operating systems use system-dependent pathname strings to name files and directories. The Javadoc definition states that * The java.nio.file package defines interfaces and classes for the Java virtual machine to access files, file attributes, and file systems.

  • This API may be used to overcome the limitations of the java.io.File class.

Use file instances to manipulate abstract file path names. Use path instances to manipulate files.

Beware that

File file = new File("/tmp/myfile.txt");

Path path = file.toPath();

// is implemented as

FileSystems.getDefault().getPath(this.getPath());

If you need to convert a file instance to a path instance hosed in the desired file system, please write

File file = new File("/tmp/myfile.txt");

Path path = fileSytem.getPath(file.getPath());

Learnt Lessons

Legacy libraries heavily use File objects in their algorithms and their public interface. Such libraries do not work with in-memory file systems.

The simplest approach is to always provide an API returning streams in addition to file system methods. It is always possible to map a stream to a file system-specific streams for read and write operations.

The more complex approach is to replace all File objects with Path objects. Ideally, the Java community should declare the File class as a deprecated one. Another solution would be to extend File with an internal path instance. This approach could break backward compatibility.


1. I regularly open issues in various open-source libraries to request an API extension to support Path. The maintainer of the library is often not aware you need Path to unit test with in-memory file systems.