A Guide To NIO2 Asynchronous File Channel

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

If you have a few years of experience in the Java ecosystem, and you're interested in sharing that experience with the community (and getting paid for your work of course), have a look at the "Write for Us" page.
Cheers. Eugen

1. Overview

In this article, we are going to explore one of the key additional APIs of the new I/O (NIO2) in Java 7, asynchronous file channel APIs.

If you are new to asynchronous channel APIs in general, we have an introductory article on this site which you can read by following this link before proceeding.

To use the NIO2 asynchronous file channels in our projects, we have to import the java.nio.channels package as it bundles all required classes:

import java.nio.channels.*;

2. The AsynchronousFileChannel

In this section, we will explore how to use the main class that enables us to perform asynchronous operations on files, the AsynchronousFileChannel class. To create an instance of it, we call the static open method:

The first parameter to the open API is a Path object representing the file location. To read more about path operations in NIO2, follow this link. The other parameters make up a set specifying options that should be available to the returned file channel.

The asynchronous file channel we have created can be used to perform all known operations on a file. To perform only a subset of the operations, we would specify options for only those. For instance, to only read:

3. Reading From a File

Just like with all asynchronous operations in NIO2, reading a file’s contents can be done in two ways. Using Future and using CompletionHandler. In each case, we use the read API of the returned channel.

Inside the test resources folder of maven or in the source directory if not using maven, let’s create a file called file.txt with only the text baeldung.com at it’s beginning. We will now demonstrate how to read this content.

3.1. The Future Approach

First, we will see how to read a file asynchronously using the Future class:

In the above code, after creating a file channel, we make use of the read API – which takes a ByteBuffer to store the content read from the channel as its first parameter.

The second parameter is a long indicating the position in the file from which to start reading.

The method returns right away whether the file has been read or not.

Next, we can execute any other code as the operation continues in the background. When we are done with executing other code, we can call the get() API which returns right away if the operation already completed as we were executing other code, or else it blocks until the operation completes.

Our assertion indeed proves that the content from the file has been read.

If we had changed the position parameter in the read API call from zero to something else, we would see the effect too. For example, the seventh character in the string baeldung.com is g. So changing the position parameter to 7 would cause the buffer to contain the string g.com.

3.2. The CompletionHandler Approach

Next, we will see how to read a file’s contents using a CompletionHandler instance:

In the above code, we use the second variant of the read API. It still takes a ByteBuffer and the start position of the read operation as the first and second parameters respectively. The third parameter is the CompletionHandler instance.

The first generic type of the completion handler is the return type of the operation, in this case, an Integer representing the number of bytes read.

The second is the type of the attachment. We have chosen to attach the buffer such that when the read completes, we can use the content of the file inside the completed callback API.

Semantically speaking, this is not really a valid unit test since we cannot do an assertion inside the completed callback method. However, we do this for the sake of consistency and because we want our code to be as copy-paste-run-able as possible.

4. Writing to a File

Java NIO2 also allows us to perform write operations on a file. Just as we did with other operations, we can write to a file in two ways. Using Future and using CompletionHandler. In each case, we use the write API of the returned channel.

Creating an AsynchronousFileChannel for writing to a file can be done like this:

4.1. Special Considerations

Notice the option passed to the open API. We can also add another option StandardOpenOption.CREATE if we want the file represented by a path to be created in case it does not already exist. Another common option is StandardOpenOption.APPEND which does not over-write existing content in the file.

We will use the following line for creating our file channel for test purposes:

This way, we will provide any arbitrary path and be sure that the file will be created. After the test exits, the created file will be deleted. To ensure the files created are not deleted after the test exits, you can remove the last option.

To run assertions, we will need to read the file content where possible after writing to them. Let’s hide the logic for reading in a separate method to avoid redundancy:

Let’s inspect what is happening in the above code. We create a random file name and use it to get a Path object. We use this path to open an asynchronous file channel with the previously mentioned options.

We then put the content we want to write to the file in a buffer and perform the write. We use our helper method to read the contents of the file and indeed confirm that it is what we expect.

4.3. The CompletionHandler Approach

We can also use the completion handler so that we don’t have to wait for the operation to complete in a while loop: