How to set the byte order of a NIO buffer

When reading data items larger than a byte from a buffer, the byte order of the various bytes making up the piece of data becomes important. The simplest example is a 2-byte value. If we call the two bytes b1 and b2, then together they represent 256 x b2 + b1. But in a file or stream (or buffer!), there are two possible ways that we can order the bytes, depending on whether we put b1 or b2 first:

When more than 2 bytes are involved, by far the most common convention is still to use either big or little endian: in other words, either the least or most numerically significant byte comes first. And so these are the two orderings supported by Java Buffer classes. (Another ordering called middle endian or mixed endian is possible but rare, and not currently supported by NIO.) To specify either big or little endian, call the following:

ByteBuffer buffer = ...
buffer.order(ByteOrder.LITTLE_ENDIAN);
// ... read/write some little-endian numbers ...
buffer.order(ByteOrder.BIG_ENDIAN);
// ... read/write some big-endian numbers ...

Byte order is thus considered to be a poperty of the buffer, not of an individual value from the buffer. (And it's arguably not so common to mix orderings within a single data structure.) But you can change a buffer's ordering at any time.

Determining the current byte order of a buffer

Usually it isn't a mystery, but if you need to find the current order of a buffer, you can call order() with no parameters. So here's how to find out if a buffer is little-endian:

if (buffer.order() == ByteOrder.LITTLE_ENDIAN) {
  System.out.println("I'm little-endian!");
}

Default byte order

A ByteBuffer's default byte order is big endian. This is the order commonly used in networking protocols and by various non-Intel hardware such (e.g. SPARC and Motorola chips are big-endian).

The byte order of a duplicate or sliced buffer

There is a gotcha to be aware of: the byte order of a buffer is not preserved when you make a duplicate or "slice" of that buffer. That is, when you create one buffer object from another, with the second in effect being a "view" of part of the original buffer. For example:

// Create a buffer and set it as little-endian
ByteBuffer origBuff = ByteBuffer.allocate(100);
origBuff.order(ByteOrder.LITTLE_ENDIAN);
// Now create a slice of the buffer
ByteBuffer slice = origBuff.slice();
// 'slice' now refers to the same data, but is BIG ENDIAN!

In the above code, slice will be a separate buffer object that is still "backed" by the same actual data. But slice will be a big-endian view of that data, because big-endian is the default. The endianness of origBuff is not transferred over when the new view of the buffer is created.

Using your hardware's native ordering

Just occasionally it may useful to read or write data to a buffer in the current hardware's "native" ordering (that is, the order in which the your machine's CPU writes words to memory). This can be determined by:

ByteOrder.nativeOrder();

which will return either ByteOrder.BIG_ENDIAN or ByteOrder.LITTLE_ENDIAN. So you can call:

buffer.order(ByteOrder.nativeOrder());

You might use this, for example, if you have written a native library that you have compiled for various architectures (which thus writes data to a file or memory in the machine's native byte order), but where you want the Java part of your application to retain the same code base across architectures. Or you might just want to make sure that you are reading and writing data in the most efficient way for your system.


If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.

Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.