Introduction to NIO.2

Ever since the dawn of Java programming there was always present certain need to communicate with file system. Whether it was to store some configuration, user-generated content or even to set up and maintain whole database for your application you were bound to face challenges of file system access from java some time in your career. Java libraries for file system access and manipulation came long way since its introduction back in ’96 when JDK 1.0 introduced package java.io.
Tools provided by versions prior to NIO.2 library were quite limited and left quit large space to be filled with native code when some more advanced functionality was required. If a programmer wanted to monitor changes that were happening in given folder the only way to do this was to go native and write platform dependent code and integrate it with his Java application. But problems with file system related operations do not end here. Even trivial operations with files required rather large amounts of code that interacted with provided interfaces. Lets consider following example: moving of a file from one location to another. Up to Java 7 there was no guarantee of atomicity for move or rename operations! So it was programmers responsibility to ensure atomicity of these basic file operations in transactional environment. In case of failure during processing of these operations one might have dealt with situation when both source and destination files existed or when destination file was not written completely.

Developers were forced to deal with these unpleasant situations which often resulted in mixing of native code with Java code, hence loosing the platform independence. Fortunately, new version of NIO library arms developers with some of the native capabilities as well as with overhauled system of managing and manipulating file systems. Development of NIO.2 library is still work in progress so we can surely expect more improvements in this area in next releases of Java. Introduction of NIO.2 is considered by many as a step forward and was welcomed change in a messy world of file system manipulation in Java.

This library update was described in JSR 203 (Java Specification Request). First versions of this renewed library were available in OpenJDK implementation and helped shape the future of IO in upcoming release of Java 7. Even though it was possible to work with file systems ever since Java 1, IO library was missing quite a lot of basic capabilities that did not appear in development kit until the release of Java 7. NIO.2 brings big changes in file system interaction, asynchronous IO and Socket Channel API.

The need for new IO library

Since the changes done to this library were so extensive one might ask why were these necessary? Compared to other Java libraries, IO library was almost left behind and its development was insufficient. Biggest problem was that there was very little support for the maintenance of files. Most noticeable shortcomings included:

Lack of provided information

One often has to perform creation, deletion, copying or moving of the files when working with file system. When it comes to information provided by older libraries in regard to the result of each of these operations we can clearly see how limited options did the programmer have when it had come to handling exceptional behavior while working with file system. As we can see simply by examining the method declaration and javadoc of any of these methods, developer is going to have a hard time determining what went wrong with his attempt to rename a file when method returns false in case of unsuccessful renaming. This unfortunate design flaw made any effort to improve code quality even harder since programmer had basically nothing to hold on to.

Performance issues

Even though the prior versions of library offered means to get the information about file or folder there is certain performance concern about the way this was done. Library was designed to do so by providing many fine-grained methods (small and modular methods with only one simple goal, for example getName()). So there was no way to get all the necessary information in one call, which often resulted in many subsequent calls to file system causing inefficiencies in performance. Solutions like these are hardly scalable since when you ask for the contents of a folder you end up with an array or a list. In case of large enough folders located in remote locations this approach causes inefficiencies not only to the remote file system but also in regard to network operations.

Limited capabilities

IO library did not support any of the following features until the release of Java 7:

There was no way to perform atomic rename, move or other basic file related operations. It might have even come to state when both source and target file existed when the moving of a file failed.

There was no way to create a file with file attributes or file access modifiers initialized during the creation process (in an atomic fashion).

There was no way to copy file attributes from source file onto the target file. Since Java 6 approach consisted of using mainly byte channel to transfer the contents of the file, lots of valuable metadata (file attributes) stayed untouched by this operation. It was not possible to directly determine the ower, group or access rights to certain file

There was no support for getting more detailed information about the file system or file store (except some pretty basic information).

There was no library level support for symbolic links without a need to write native code. If developer wanted to work with symbolic links – traversing, moving or copying – they were forced to write these quite complex algorithms themselves. The complexity of handling circular references presented difficult problem to overcome.

There was no way to examine the type of data stored in the files. If developer had to check for certain file type, they often resorted to guessing based on few first bytes of the file.

There was no way to extend the capabilities of library to include custom file systems such as memory based file system or encrypted file system.

There was no way to ‘listen’ to changes that happened in specified location. The only way to achieve this functionality was to implement own proprietary code that was periodically querying file system for changes (or again – go native on this problem based on concrete platform and file system).

These are the most problematic areas which were addressed in NIO.2. Almost all of these topics will be addressed in more detail in the upcoming articles.