This article is a beginner’s tutorial on Java NIO (New IO). We will take a high-level look at this API which provides an alternative to Java IO. The Java NIO API can be viewed here. The example code demonstrates the use of the core abstractions in this topic.
Java nio tutorials examples in this article was built and run using:
- Java 1.8.101 (1.8.x will do fine)
- Maven 3.3.9 (3.3.x will do fine)
- Spring source tool suite 4.6.3 (Any Java IDE would work)
- Ubuntu 16.04 (Windows, Mac or linux)
1. Introduction
Since Java 1.4 the Java NIO API has provided an alternate method of dealing with IO operations. Why did we need an alternate method for doing IO? As time progresses new problem sets arrive and new approaches to solving these problems are thought of. To understand the need for an alternate means of IO handling one should probably understand the core differences between the two approaches.
IO | NIO |
|---|---|
| Core differences: | Core differences: |
| Stream oriented processing | Uses buffers |
| Blocking in processing | Non blocking in processing |
| Good for: | Good for: |
| High data volume with low simultaneous open file descriptor counts
(eg: less client connections with more data chunks per connection) | Less data volume with high simultaneous open file descriptor counts
(eg: More connections with smaller / infrequent “chunks” of data) |
NIO puts us in a position to make more judicious use of server / machine resources. By bringing us closer to the metal with an intelligent selection of abstractions we are able to better apply finite server resources to meet the increasing demands of modern day scale.
2. Java NIO
A quick glance at the summary of the Java NIO API reveals to us the core abstractions one should be familiar with when working with Java NIO. These are:
- Buffers : A container to hold data for the purposes of reading and or writing.
- Channels : An abstraction for dealing with an open connection to some component that is performing some kind of IO operation at a hardware level.
- Charsets : Contains charsets, decoders and encoders for translating between bytes and unicode.
- Selectors : A means to work with multiple channels via one abstraction.
2.1 Buffers
A Buffer is a container for a fixed size of data of a specific primitive type (char, byte, int, long, float etc). ABuffer has content, a position, a limit and capacity. It can flip, rewind, mark and reset its position reinforcing the core differences between NIO and IO (buffer vs stream).
- Capacity = number of elements it contains.
- Limit = index of element that must not be read or written.
- Position = index of next element to read or write.
- Flip = invert position and limit when toggling the IO operation on aBuffer. (eg: write out to console what we just read from aChannel into the Buffer).
- Rewind = sets position to 0 and leaves limit unchanged in order to re-read theBuffer.
- Mark = bookmarks a position in theBuffer.
- Reset = resets the position to the previous mark.
What does all that mean? Well basically we put content into aBuffer (either read it from aChannel or put it directly into the Buffer with the intent to write it to aChannel).
We then advance the cursor through the content of theBuffer as we read or write. We flip aBuffer to change our IO operation on theBuffer (ie: go from reading to writing).
The capacity represents the total capacity theBuffer can hold with regard to content. The actual metric used for measurement depends on the type of theBuffer. (eg:CharBuffer capacity measured in characters andByteBuffer capacity measured in Bytes).
2.1.1 Java nio tutorial Example usage of a ByteBuffer
Reading from Channel into ByteBuffer
01 02 03 04 05 06 07 08 09 10 11 | ...finalByteBuffer buffer = createBuffer();while(fileChannel.read(buffer) != -1) { contents.append(newString(buffer.array())); buffer.clear();}...privateByteBuffer createBuffer() { returnByteBuffer.allocate(BYTE_BUFFER_LENGTH);}... |
- line 2: AByteBuffer is created with a defined capacity. (BYTE_BUFFER_LENGTH)
- line 3: Data is read from the specifiedFileChannel into theByteBuffer.
- line 4: TheByteBuffer’s current contents are added to theStringBuilder. This is done via convenience method
array()as a result of the way theByteBuffer was created in the example (viaallocate()). - line 5: TheByteBuffer is cleared to prepare for reading more data from the channel, this will set the position cursor back to 0 and allow contents to be read from theFileChannel back into theByteBuffer repeating the process until no more data is available.
Alternate method for reading from Channel into ByteBuffer
1 2 3 4 5 6 7 8 | ...buffer.flip();if(buffer.hasRemaining()) { byte[] src =newbyte[buffer.limit()]; buffer.get(src); contents.append(newString(src));}.... |
- line 2: Invert the position and limit of theBuffer to retrieve what has been read from theChannel.
- line 3: Ensure there is something to read, ie: The difference between limit and position is > 0.
- line 4: Create a byte array to be the size of the data in theBuffer.
- line 5: Retrieve the contents of theBuffer into the byte array.
- line 6: Create aString array from the contents of the byte array.
It is important to also note that the instantiation of a newString to hold the bytes implicitly uses the defaultCharset to decode the bytes from their byte values to their corresponding unicode characters. If the defaultCharset was not what we were looking for, then instantiating a newString with the appropriateCharset would be required.
2.2 Channels
AChannel is a proxy (open connection proxy) to a component that is responsible for native IO (file or network socket). By acting as a proxy to some native IO component we are able to write and / or read from aChannel. SomeChannel implementations allow us to put them into non-blocking mode allowing read and write operations to be non-blocking. The sameChannel can be used for both reading and writing.
AChannel is open upon creation and remains that way until it is closed.
2.2.1 Example usage of a FileChannel
Creating a FileChannel
1 2 3 4 5 6 | ...finalFile file =newFile(FileChannelReadExample.class.getClassLoader().getResource(path).getFile());returnfileOperation == FileOperation.READ ?newFileInputStream(file).getChannel() : newFileOutputStream(file).getChannel();... |
- line 3: Create aFile Object
- line 4: Depending on the type ofFile operation (read or write) we create the necessary Stream and get theChannel from the Stream.
2.3 Charsets
ACharset is a mapping between 16 bit unicode characters and bytes. Charsets work with decoders and encoders which facilitate the adaption from bytes to characters and vice versa.
- Encoding: The process of transforming a sequence of characters into bytes
- Decoding: The process of transforming bytes into character buffers.
Charset provides other utility methods for looking up aCharset by name, creating coders (encoder or decoders) and getting the default Charset. Typically when one works withByteBuffer andString as is the case in the example, the defaultCharset is what we would normally use if we do not explicitly specify one. This would suffice most of the time.
Charset usage
01 02 03 04 05 06 07 08 09 10 11 12 13 | ...finalCharset defaultCharset = Charset.defaultCharset();finalString text ="Lorem ipsum"; finalByteBuffer bufferA = ByteBuffer.wrap(text.getBytes());finalByteBuffer bufferB = defaultCharset.encode(text); finalString a =newString(bufferA.array());finalCharBuffer charBufferB = defaultCharset.decode(bufferB); System.out.println(a);System.out.println(newString(charBufferB.array()));... |
- line 2: The defaultCharset is retrieved.
- line 5: The sample text is wrapped in aByteBuffer. The defaultCharset is used implicitly when encoding the characters into bytes.
- line 6: The sample text is encoded explicitly using the defaultCharset encoder.
- line 8: AString is created using the defaultCharset decoder implicitly .
- line 9: A Character Buffer (ultimately a String) is created using the defaultCharset decoder explicitly.
2.4 Selectors
Selectors as the name implies, select from multipleSelectableChannel types and notify our program when IO has happened on one of those channels. It is important to note that during the registration process (registering aSelectableChannel with aSelector) we declare the IO events we are interested in, termed the “interest set” This can be:

Thank you!
We will contact you soon.
- Connect
- Accept
- Read
- Write
With this proxy in place and the added benefit of setting thoseSelectableChannel types into non-blocking mode we are able tomultiplex over said channels in a very efficient way, typically with very few threads, even as little as one.
Selector usage with SelectableChannel
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | try(finalSelector selector = Selector.open(); finalServerSocketChannel serverSocket = ServerSocketChannel.open();) { finalInetSocketAddress hostAddress = newInetSocketAddress(Constants.HOST, Constants.PORT); serverSocket.bind(hostAddress); serverSocket.configureBlocking(false); serverSocket.register(selector, serverSocket.validOps(),null); while(true) { finalintnumSelectedKeys = selector.select(); if(numSelectedKeys >0) { handleSelectionKeys(selector.selectedKeys(), serverSocket); } }} |
- line 1: We create aSelector using the systems defaultSelectorProvider.
- line 2: We create aServerSocketChannel which is aSelectableChannel.
- line 6: We configure theServerSocketChannel for non-blocking mode.
- line 7: We then register theServerSocketChannel with theSelector, we receive aSelectionKey from the registration although I discard it, having no use for it. The
serverSocket.validOps()call will return an operation set that is supported by theChannel, which in this case is only the “Accept Connection” event. The returnedSelectionKey contains an “interest set” which indicates the set of IO events theSelector must monitor theChannel for. - line 10: We call
select()on theSelector which is blocking until some IO occurs on any of theSelectableChannel instances that are registered with it. It will return the number of keys which are ready for IO activity.
The following code snippet demonstrates iterating through all theSelectionKey instances that indicate IO “ready” events fromChannel instances managed by the singleSelector. We are only interested in “Accept” and Readable” events. For every new connection accepted an “Accept” event is signaled and we can act on it. Likewise with a “read” ready event we can read incoming data. It is important to remove the SelectionKey from the set after handling it, as theSelector does not do this and you will continue to process that stale event.
Working with SelectionKeys
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | finalIterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();while(selectionKeyIterator.hasNext()) { finalSelectionKey key = selectionKeyIterator.next(); if(key.isAcceptable()) { acceptClientSocket(key, serverSocket); }elseif(key.isReadable()) { readRequest(key); }else{ System.out.println("Invalid selection key"); } selectionKeyIterator.remove();} |
- line 13: Remember to remove theSelectionKey from the selected set as theSelector does not do this for us, if we don’t do it, we will continue to process stale events.
The following code snippet demonstrates the use of registration of aSocketChannel with the sameSelector that manages theServerSocketChannel. Here, however, the interest set is only for IO “read” events.
Registering a Channel with a Selector
1 2 3 | finalSocketChannel client = serverSocket.accept();client.configureBlocking(false);client.register(key.selector(), SelectionKey.OP_READ); |
3. Summary
In this beginners tutorial we understood some of the differences between IO and NIO and reasons for NIO’s existence and applicability. We have also covered the 4 main abstractions when working with NIO. Those are:
- Buffers
- Channels
- Selectors
- Charsets
We have seen how they can be used and how they work in tandem with each other. With this tutorial in hand, you understand the basics of creating Channels and using them with Buffers. How to interact with Buffers and the rich API it provides for traversing buffer content. We have also learnt how to register Channels with Selectors and interact with theSelector via itsSelectionKey abstraction.
4. Working with Java NIO tutorial example source code
The source code contains the following examples:
- Charset example.
- FileChannel example. This example reads from a classpath resource file
src/main/resources/file/input.txtand writes a String literal to a classpath resourcesrc/main/resources/file/output.txt.Be sure to check the foldertarget/classes/filewhen wanting to view the output of the write example. - Client Server example. Start the server first, then start the client. The client will attempt 10 connections to the server and write the same text 10 times to the server which will simply write the contents to console.
5. Download the source code
This was a Java Nio Tutorial for Beginners Example.
You can download the full source code of this example here:Java Nio tutorial for beginners
Last updated on May 27th, 2020

Thank you!
We will contact you soon.





