JavaData & JavaJava NIO.2 File Handling

Java NIO.2 File Handling content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

The primary use of Java NIO is to access files via a channel and buffers. The NIO.2 enhanced it to stream-based I/O as well. Therefore, NIO now provides the capability for channel-based I/O, stream-based I/O, and for path and file system operations. New features added to NIO.2 extensively extended the capability of its predecessor. This article picks up the features of channel-based I/O and shows its uses in a brief manner.

An Overview

The Java NIO system is built upon two fundamental structures, called buffer and channels. Buffer offers a staging area where data is stored and retrieved later. It acts as a temporary repository between I/O transfers. The NIO buffer works hand in glove with the channel; if data is to travel en route through a channel, buffer represents the carrier on which the data is carried. Channel represents a open connection to an I/O device like, a file, socket, and so forth. Imagine a pneumatic tube where you write data in a buffer corresponding to the source of the channel and, at the other end of the tube, a response buffer is created so that it may be drained to get the reply from the receiving end. The buffer thus acts as the carrier through the open connection of channels.

The improved NIO.2 capabilities include more that just channels. It now can be used for stream-based I/O as well. But that does not make channel-based I/O lesser in importance. In fact, the asynchronous channel API introduced with NIO.2 is an extension of the old channel. The asynchronous channel is a connection that supports multiple I/O operations in parallel by using separate threads. This capability particularly enhances the performance in multi-threaded I/O operations. In contrast to this, synchronous I/O operates in one thread per I/O connection mode. Therefore, it has a limitation both in terms of scalability and performance. Asynchronous I/O APIs are built to overcome this restriction of overlapping I/O, which is particularly suitable for operations that take a large amount of time. This, however, does not make synchronous I/O APIs obsolete; rather, it specializes its utility for single-threaded, relatively fast I/O operations.

Read Operations with Channel-based I/O

To understand channel-based I/O better, let’s implement a simple program in Java. Because most common I/O operations are performed on a disk file, we’ll take a regular file to implement the I/O operations.

But, before performing any file I/O, we first must have access to the file. This can be achieved by creating a Path object. The Path is an interface, an instance of which is used to locate a file in the file system. Therefore, we may invoke the static method called get() defined in the Paths class. This class contains only two overloaded static methods that return a Path object; one converts a path string and another uses URI.

static Path get(String first, String ...more)
static Path get(URI uri)

Once we get access to the file descriptor of the file, we may open it in one of various ways, depending on our need. For example, if we want to read/write bytes in an asynchronous channel, we may create an instance of AsynchronousByteChannel. If we want to create a seekable byte channel connected to a file that contains a sequence of variable length bytes, we may create an instance of SeekableByteChannel. There are others, such as GatheringByteChannel, that create a channel to writes bytes from a sequence of buffers, ScatteringByteChannel, MulticastChannel and so on. Refer to the Java API documentation for details on them.

Now, to create an instance of SeekableByteChannel for our purpose, we may invoke the static method called newByteChannel() defined in the Files class of the java.nio.files package.

File channel operations are based on bytes. As a result, the buffer we’ll be using is called ByteBuffer. The ByteBuffer uses a backing array designated by the capacity supplied during its creation. The read() method of the channel reads a sequence of bytes from the given channel and puts it in the backing array. Once the reading is done, the buffer must be rewound so that the current position of the buffer is set to the 0th location. The byte data then can be read by its index position.

The following code illustrates the idea.

public static void readFile(String filePath) {
   try (SeekableByteChannel channel =
         Files.newByteChannel(Paths.get(filePath))) {
      ByteBuffer buf = ByteBuffer.allocate(255);
      int c = 0;
      do {
         // Reads a sequence of bytes from this
         // channel into the given
         // buffer the position is updated with
         // the number of bytes actually read
         c =;

         if (c != -1) {
            // Rewinds buffer, the position is set to zero
            for (int i = 0; i < c; i++)
      } while (c != -1);
   } catch (IOException | InvalidPathException ex) {

Write Operation with Channel-based I/O

The write operation is almost identical to the read operation as illustrated previously. The idea is to create a channel in write mode. Because here also we’ll use a disk file for the purpose, a file is opened with the options WRITE and CREATE. There are various such options defined in enum java.nio.file.StandardOpenOption. Some of them are as follows:

  • APPEND: File is open in append mode
  • CREATE: Creates a file if it doesn’t already exist
  • CREATE_NEW: Creates a new file, failing if the file already exists
  • DELETE_ON_CLOSE: Deletes the file after it is closed
  • READ: File opened in read mode
  • WRITE: File opened in write mode

There are several ways to read and write data via channels. The illustrated code snippets are just a few of them. The next sample shows that we have opened the channel with a call to the newByteChannel() method, typecasting it to a FileChannel object. A buffer is allocated according to the length of the data. The rewind() method of he buffer is called to set the current position at the first location. Then, the write() method of the channel is invoked with the buffer object as its parameter. The contents of the buffer finally get written into the specified file.

public static void writeFile(String filePath, String data) {
   try (FileChannel channel = (FileChannel)
   StandardOpenOption.WRITE)) {
      ByteBuffer buf = ByteBuffer.allocate(data.length());
      for (int i = 0; i < data.length(); i++) {
         buf.put((byte) (data.charAt(i) + ' '));
      // Rewinds buffer, the position is set to zero
   } catch (InvalidPathException | IOException ex) {

A Quick Comparison with Stream-based I/O

It is worth mentioning that, although stream-based I/O is analogous to channel-based I/O, the channel-based I/O schema has changed the scheme of slow processing the stream-oriented I/O in at least three ways:

  • Firstly, channel-based I/O supports two-way, read-write operations in contrast to one-way read or write operations in stream-based I/O.
  • It is now possible to do asynchronous read write operations with channels.
  • Channel always uses a buffer for read-write operations. Data sent to a channel must be stored in a buffer before realizing any read-write operation. The backing buffer makes it convenient to use with multiple threads.


If you have worked with NIO, you probably noticed that NIO.2 takes significant advantage of the Path interface. This feature was absent with its predecessor. The try-with resource we have used is also a post-JDK 7 incorporation. NIO.2 extended the capability of channel-based I/O to a degree where it is not only convenient and easy to code but also makes the I/O operations efficient.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories