Whenever you read from or write to the buffer, only it's position changes.
So you are wrong about reading from channel. Note that reading from channel means writing to the buffer. You can set the position to 0 and limit - to the maximum number of bytes to read before you call Channel.read(ByteBuffer). The read operation then updates the position to the number of bytes actually read. So, to start reading from buffer you have to call ByteBuffer.flip().
Your buffer is never filled. bytes.array() simply retrieves the backing byte array. If you write anything to this then the ByteBuffer fields - except the array itself of course - are unaffected. So the position stays at zero.
What you are doing in in.read(bytes.array()) is identical to byte[] tmp = bytes.array() followed by in.read(tmp). Changes to the tmp variable cannot be reflected in the bytes instance. The backing array is changed which may mean that the contents of the ByteBuffer is changed as well. But the offsets into the backing byte array - including the position and limit - aren't.
You should only fill the ByteBuffer using any of the put methods (that do not take an index) such as put(byte[]).
I'll provide a code fragment that may get you thinking on how to handle strings, encodings and character and byte buffers:
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.StandardCharsets;
public class TestBuffer {
private static final String testStr = "Stackoverflow is a great place to discuss tech stuff!";
private static final boolean END_OF_INPUT = true;
private ByteBuffer bytes = ByteBuffer.allocate(1000);
public TestBuffer() {
System.out.println("init : " + bytes.toString());
}
public static void main(String a[]) {
TestBuffer buf = new TestBuffer();
buf.writeBuffer();
buf.readBuffer();
}
// write testStr to buffer
private void writeBuffer() {
CharBuffer testBuffer = CharBuffer.wrap(testStr);
CharsetEncoder utf8Encoder = StandardCharsets.UTF_8.newEncoder();
CoderResult result = utf8Encoder.encode(testBuffer, bytes, END_OF_INPUT);
if (result.isError()) {
bytes.clear();
throw new IllegalArgumentException("That didn't go right because " + result.toString());
}
if (result.isOverflow()) {
bytes.clear();
throw new IllegalArgumentException("Well, too little buffer space.");
}
System.out.println("written: " + bytes.toString());
bytes.flip();
}
// read buffer data back to byte array and print
private void readBuffer() {
byte[] b = new byte[bytes.remaining()];
bytes.get(b);
System.out.println("data : " + new String(b, StandardCharsets.UTF_8));
System.out.println("read : " + bytes.toString());
bytes.clear();
}
}
Note that buffers and streams are really two separate ways of handling sequential data. If you are trying to use both of them at the same time you may be trying to be too clever.
You could also solve this without CharBuffer and ByteBuffer using a byte[] buffer and a StringReader wrapped by a ReaderInputStream.
That Android piece of code completely abuses the ByteBuffer. It should just have created a byte[] and wrapped that, setting the limit to the capacity. Whatever you do, do not use it as an example on ByteBuffer handling. It made my eyes water in disgust. Code like that is a bug waiting to happen.
You are not writting anything in the writeBuffer() method.
You may use something like bytes.put(b).
Its best illustrated HERE in this article: They are mainly different depending on the mode,
- In Write Mode, Capacity and Limit are Same.
- But in Read mode Limit means the limit of how much data you can read from the data

ByteBuffer does not have a length() method. Instead it has a several length-like concepts:
mark <= position <= limit <= capacity
capacity = Inside the ByteBuffer, there is a backing byte[] or something that behaves much like one. The capacity is its size. The capacity indexes the first slot past the end of the buffer.
limit = When filling the buffer, the limit is the same as the capacity. When emptying the buffer, it is one past the last filled byte in the buffer.
position = When filling the buffer, the position points just past the last byte filled in the buffer. When emptying the buffer, the position points just past the last byte written from the buffer.
mark The mark is an optional bookmark to let you record an interesting spot in the ByteBuffer that you want to return to later. When you take a mark() it records current position, and when you call reset() it restores that position.
I hope this helps. Also an example can be seen over here: http://mindprod.com/jgloss/bytebuffer.html
Source: Oracle Java Buffer reference - See 'Invariants' section.