[Java][FlexBuffers] Abstract buffer access from ByteBuffer (#5743)

To read and build flexbuffers on Java, one needs to wrap the data
using  ByteBuffer. But for the common case of having ByteBuffers
backed by arrays, accessing from a ByteBuffer might be inefficient.

So this change introduces two interfaces: ReadBuf and ReadWriteBuf.
It allows one to read and writes data directly on an array. It also allow
 other buffer implementations to be used with flexbuffers.

Another change is that FlexBuffersBuilder backed by array allows
the buffer to grow with the increase of the message size. Something
that could not be done with ByteBuffer.
diff --git a/java/com/google/flatbuffers/ArrayReadWriteBuf.java b/java/com/google/flatbuffers/ArrayReadWriteBuf.java
new file mode 100644
index 0000000..0051774
--- /dev/null
+++ b/java/com/google/flatbuffers/ArrayReadWriteBuf.java
@@ -0,0 +1,241 @@
+package com.google.flatbuffers;
+
+import java.util.Arrays;
+
+/**
+ * Implements {@code ReadBuf} using an array of bytes
+ * as a backing storage. Using array of bytes are
+ * usually faster than {@code ByteBuffer}.
+ *
+ * This class is not thread-safe, meaning that
+ * it must operate on a single thread. Operating from
+ * multiple thread leads into a undefined behavior
+ */
+public class ArrayReadWriteBuf implements ReadWriteBuf {
+
+  private byte[] buffer;
+  private int writePos;
+
+  public ArrayReadWriteBuf() {
+    this(10);
+  }
+
+  public ArrayReadWriteBuf(int initialCapacity) {
+    this(new byte[initialCapacity]);
+  }
+
+  public ArrayReadWriteBuf(byte[] buffer) {
+    this.buffer = buffer;
+    this.writePos = 0;
+  }
+
+  public ArrayReadWriteBuf(byte[] buffer, int startPos) {
+    this.buffer = buffer;
+    this.writePos = startPos;
+  }
+
+  @Override
+  public boolean getBoolean(int index) {
+    return buffer[index] != 0;
+  }
+
+  @Override
+  public byte get(int index) {
+    return buffer[index];
+  }
+
+  @Override
+  public short getShort(int index) {
+    return (short) ((buffer[index+ 1] << 8) | (buffer[index] & 0xff));
+  }
+
+  @Override
+  public int getInt(int index) {
+    return (((buffer[index + 3]) << 24) |
+      ((buffer[index + 2] & 0xff) << 16) |
+      ((buffer[index + 1] & 0xff) << 8) |
+      ((buffer[index] & 0xff)));
+  }
+
+  @Override
+  public long getLong(int index) {
+    return ((((long) buffer[index++] & 0xff)) |
+      (((long) buffer[index++] & 0xff) << 8) |
+      (((long) buffer[index++] & 0xff) << 16) |
+      (((long) buffer[index++] & 0xff) << 24) |
+      (((long) buffer[index++] & 0xff) << 32) |
+      (((long) buffer[index++] & 0xff) << 40) |
+      (((long) buffer[index++] & 0xff) << 48) |
+      (((long) buffer[index]) << 56));
+  }
+
+  @Override
+  public float getFloat(int index) {
+    return Float.intBitsToFloat(getInt(index));
+  }
+
+  @Override
+  public double getDouble(int index) {
+    return Double.longBitsToDouble(getLong(index));
+  }
+
+  @Override
+  public String getString(int start, int size) {
+    return Utf8Safe.decodeUtf8Array(buffer, start, size);
+  }
+
+  @Override
+  public byte[] data() {
+    return buffer;
+  }
+
+
+  @Override
+  public void putBoolean(boolean value) {
+      setBoolean(writePos, value);
+      writePos++;
+  }
+
+  @Override
+  public void put(byte[] value, int start, int length) {
+    set(writePos, value, start, length);
+    writePos+=length;
+  }
+
+  @Override
+  public void put(byte value) {
+    set(writePos, value);
+    writePos++;
+  }
+
+  @Override
+  public void putShort(short value) {
+    setShort(writePos, value);
+    writePos +=2;
+  }
+
+  @Override
+  public void putInt(int value) {
+    setInt(writePos, value);
+    writePos +=4;
+  }
+
+  @Override
+  public void putLong(long value) {
+    setLong(writePos, value);
+    writePos +=8;
+  }
+
+  @Override
+  public void putFloat(float value) {
+    setFloat(writePos, value);
+    writePos +=4;
+  }
+
+  @Override
+  public void putDouble(double value) {
+    setDouble(writePos, value);
+    writePos +=8;
+  }
+
+  @Override
+  public void setBoolean(int index, boolean value) {
+    set(index, value ? (byte)1 : (byte)0);
+  }
+
+  @Override
+  public void set(int index, byte value) {
+    requestCapacity(index + 1);
+    buffer[index] = value;
+  }
+
+  @Override
+  public void set(int index, byte[] toCopy, int start, int length) {
+    requestCapacity(index + (length - start));
+    System.arraycopy(toCopy, start, buffer, index, length);
+  }
+
+  @Override
+  public void setShort(int index, short value) {
+    requestCapacity(index + 2);
+
+    buffer[index++] = (byte) ((value) & 0xff);
+    buffer[index  ] = (byte) ((value >> 8) & 0xff);
+  }
+
+  @Override
+  public void setInt(int index, int value) {
+    requestCapacity(index + 4);
+
+    buffer[index++] = (byte) ((value) & 0xff);
+    buffer[index++] = (byte) ((value >>  8) & 0xff);
+    buffer[index++] = (byte) ((value >> 16) & 0xff);
+    buffer[index  ] = (byte) ((value >> 24) & 0xff);
+  }
+
+  @Override
+  public void setLong(int index, long value) {
+    requestCapacity(index + 8);
+
+    int i = (int) value;
+    buffer[index++] = (byte) ((i) & 0xff);
+    buffer[index++] = (byte) ((i >>  8) & 0xff);
+    buffer[index++] = (byte) ((i >> 16) & 0xff);
+    buffer[index++] = (byte) ((i >> 24) & 0xff);
+    i = (int) (value >> 32);
+    buffer[index++] = (byte) ((i) & 0xff);
+    buffer[index++] = (byte) ((i >>  8) & 0xff);
+    buffer[index++] = (byte) ((i >> 16) & 0xff);
+    buffer[index  ] = (byte) ((i >> 24) & 0xff);
+  }
+
+  @Override
+  public void setFloat(int index, float value) {
+    requestCapacity(index + 4);
+
+    int iValue = Float.floatToRawIntBits(value);
+    buffer[index++] = (byte) ((iValue) & 0xff);
+    buffer[index++] = (byte) ((iValue >>  8) & 0xff);
+    buffer[index++] = (byte) ((iValue >> 16) & 0xff);
+    buffer[index  ] = (byte) ((iValue >> 24) & 0xff);
+  }
+
+  @Override
+  public void setDouble(int index, double value) {
+    requestCapacity(index + 8);
+
+    long lValue = Double.doubleToRawLongBits(value);
+    int i = (int) lValue;
+    buffer[index++] = (byte) ((i) & 0xff);
+    buffer[index++] = (byte) ((i >>  8) & 0xff);
+    buffer[index++] = (byte) ((i >> 16) & 0xff);
+    buffer[index++] = (byte) ((i >> 24) & 0xff);
+    i = (int) (lValue >> 32);
+    buffer[index++] = (byte) ((i) & 0xff);
+    buffer[index++] = (byte) ((i >>  8) & 0xff);
+    buffer[index++] = (byte) ((i >> 16) & 0xff);
+    buffer[index  ] = (byte) ((i >> 24) & 0xff);
+  }
+
+  @Override
+  public int limit() {
+    return writePos;
+  }
+
+  @Override
+  public int writePosition() {
+    return writePos;
+  }
+
+  @Override
+  public boolean requestCapacity(int capacity) {
+    if (buffer.length > capacity) {
+      return true;
+    }
+    // implemented in the same growing fashion as ArrayList
+    int oldCapacity = buffer.length;
+    int newCapacity = oldCapacity + (oldCapacity >> 1);
+    buffer = Arrays.copyOf(buffer, newCapacity);
+    return true;
+  }
+}
diff --git a/java/com/google/flatbuffers/ByteBufferReadWriteBuf.java b/java/com/google/flatbuffers/ByteBufferReadWriteBuf.java
new file mode 100644
index 0000000..79ad7bb
--- /dev/null
+++ b/java/com/google/flatbuffers/ByteBufferReadWriteBuf.java
@@ -0,0 +1,165 @@
+package com.google.flatbuffers;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class ByteBufferReadWriteBuf implements ReadWriteBuf {
+
+  private final ByteBuffer buffer;
+
+  public ByteBufferReadWriteBuf(ByteBuffer bb) {
+    this.buffer = bb;
+    this.buffer.order(ByteOrder.LITTLE_ENDIAN);
+  }
+
+  @Override
+  public boolean getBoolean(int index) {
+    return get(index) != 0;
+  }
+
+  @Override
+  public byte get(int index) {
+    return buffer.get(index);
+  }
+
+  @Override
+  public short getShort(int index) {
+    return buffer.getShort(index);
+  }
+
+  @Override
+  public int getInt(int index) {
+    return buffer.getInt(index);
+  }
+
+  @Override
+  public long getLong(int index) {
+    return buffer.getLong(index);
+  }
+
+  @Override
+  public float getFloat(int index) {
+    return buffer.getFloat(index);
+  }
+
+  @Override
+  public double getDouble(int index) {
+    return buffer.getDouble(index);
+  }
+
+  @Override
+  public String getString(int start, int size) {
+    return Utf8Safe.decodeUtf8Buffer(buffer, start, size);
+  }
+
+  @Override
+  public byte[] data() {
+    return buffer.array();
+  }
+
+  @Override
+  public void putBoolean(boolean value) {
+    buffer.put(value ? (byte)1 : (byte)0);
+  }
+
+  @Override
+  public void put(byte[] value, int start, int length) {
+    buffer.put(value, start, length);
+  }
+
+  @Override
+  public void put(byte value) {
+    buffer.put(value);
+  }
+
+  @Override
+  public void putShort(short value) {
+    buffer.putShort(value);
+  }
+
+  @Override
+  public void putInt(int value) {
+    buffer.putInt(value);
+  }
+
+  @Override
+  public void putLong(long value) {
+    buffer.putLong(value);
+  }
+
+  @Override
+  public void putFloat(float value) {
+    buffer.putFloat(value);
+  }
+
+  @Override
+  public void putDouble(double value) {
+    buffer.putDouble(value);
+  }
+
+  @Override
+  public void setBoolean(int index, boolean value) {
+    set(index, value ? (byte)1 : (byte)0);
+  }
+
+  @Override
+  public void set(int index, byte value) {
+    requestCapacity(index + 1);
+    buffer.put(index, value);
+  }
+
+  @Override
+  public void set(int index, byte[] value, int start, int length) {
+    requestCapacity(index + (length - start));
+    int curPos = buffer.position();
+    buffer.position(index);
+    buffer.put(value, start, length);
+    buffer.position(curPos);
+  }
+
+  @Override
+  public void setShort(int index, short value) {
+    requestCapacity(index + 2);
+    buffer.putShort(index, value);
+  }
+
+  @Override
+  public void setInt(int index, int value) {
+    requestCapacity(index + 4);
+    buffer.putInt(index, value);
+  }
+
+  @Override
+  public void setLong(int index, long value) {
+    requestCapacity(index + 8);
+    buffer.putLong(index, value);
+  }
+
+  @Override
+  public void setFloat(int index, float value) {
+    requestCapacity(index + 4);
+    buffer.putFloat(index, value);
+  }
+
+  @Override
+  public void setDouble(int index, double value) {
+    requestCapacity(index + 8);
+    buffer.putDouble(index, value);
+  }
+
+  @Override
+  public int writePosition() {
+    return buffer.position();
+  }
+
+  @Override
+  public int limit() {
+    return buffer.limit();
+  }
+
+  @Override
+  public boolean requestCapacity(int capacity) {
+    return capacity <= buffer.limit();
+  }
+
+}
diff --git a/java/com/google/flatbuffers/FlexBuffers.java b/java/com/google/flatbuffers/FlexBuffers.java
index ff8ea8d..d7df08c 100644
--- a/java/com/google/flatbuffers/FlexBuffers.java
+++ b/java/com/google/flatbuffers/FlexBuffers.java
@@ -36,7 +36,7 @@
  * <p>
  * Example of usage:
  * <pre>
- * ByteBuffer bb = ... // load message from file or network
+ * ReadBuf bb = ... // load message from file or network
  * FlexBuffers.Reference r = FlexBuffers.getRoot(bb); // Reads the root element
  * FlexBuffers.Map map = r.asMap(); // We assumed root object is a map
  * System.out.println(map.get("name").asString()); // prints element with key "name"
@@ -100,7 +100,7 @@
     /** Represent a vector of booleans type */
     public static final int FBT_VECTOR_BOOL = 36;  // To Allow the same type of conversion of type to vector type
 
-    private static final ByteBuffer EMPTY_BB = ByteBuffer.allocate(1).asReadOnlyBuffer();
+    private static final ReadBuf EMPTY_BB = new ArrayReadWriteBuf(new byte[] {0}, 1);
 
     /**
      * Checks where a type is a typed vector
@@ -151,13 +151,13 @@
     }
 
     // return position of the element that the offset is pointing to
-    private static int indirect(ByteBuffer bb, int offset, int byteWidth) {
-        // we assume all offset fits on a int, since ByteBuffer operates with that assumption
+    private static int indirect(ReadBuf bb, int offset, int byteWidth) {
+        // we assume all offset fits on a int, since ReadBuf operates with that assumption
         return (int) (offset - readUInt(bb, offset, byteWidth));
     }
 
     // read unsigned int with size byteWidth and return as a 64-bit integer
-    private static long readUInt(ByteBuffer buff, int end, int byteWidth) {
+    private static long readUInt(ReadBuf buff, int end, int byteWidth) {
         switch (byteWidth) {
             case 1: return byteToUnsignedInt(buff.get(end));
             case 2: return shortToUnsignedInt(buff.getShort(end));
@@ -168,12 +168,12 @@
     }
 
     // read signed int of size byteWidth and return as 32-bit int
-    private static int readInt(ByteBuffer buff, int end, int byteWidth) {
+    private static int readInt(ReadBuf buff, int end, int byteWidth) {
         return (int) readLong(buff, end, byteWidth);
     }
 
     // read signed int of size byteWidth and return as 64-bit int
-    private static long readLong(ByteBuffer buff, int end, int byteWidth) {
+    private static long readLong(ReadBuf buff, int end, int byteWidth) {
         switch (byteWidth) {
             case 1: return buff.get(end);
             case 2: return buff.getShort(end);
@@ -183,7 +183,7 @@
         }
     }
 
-    private static double readDouble(ByteBuffer buff, int end, int byteWidth) {
+    private static double readDouble(ReadBuf buff, int end, int byteWidth) {
         switch (byteWidth) {
             case 4: return buff.getFloat(end);
             case 8: return buff.getDouble(end);
@@ -192,12 +192,23 @@
     }
 
     /**
-     * Reads a FlexBuffer message in ByteBuffer and returns {@link Reference} to
+     * Reads a FlexBuffer message in ReadBuf and returns {@link Reference} to
      * the root element.
-     * @param buffer ByteBuffer containing FlexBuffer message
+     * @param buffer ReadBuf containing FlexBuffer message
      * @return {@link Reference} to the root object
      */
+    @Deprecated
     public static Reference getRoot(ByteBuffer buffer) {
+        return getRoot( buffer.hasArray() ? new ArrayReadWriteBuf(buffer.array(), buffer.limit()) : new ByteBufferReadWriteBuf(buffer));
+    }
+
+        /**
+     * Reads a FlexBuffer message in ReadBuf and returns {@link Reference} to
+     * the root element.
+     * @param buffer ReadBuf containing FlexBuffer message
+     * @return {@link Reference} to the root object
+     */
+    public static Reference getRoot(ReadBuf buffer) {
         // See Finish() below for the serialization counterpart of this.
         // The root ends at the end of the buffer, so we parse backwards from there.
         int end = buffer.limit();
@@ -213,17 +224,17 @@
     public static class Reference {
 
         private static final Reference NULL_REFERENCE = new Reference(EMPTY_BB, 0, 1, 0);
-        private ByteBuffer bb;
+        private ReadBuf bb;
         private int end;
         private int parentWidth;
         private int byteWidth;
         private int type;
 
-        Reference(ByteBuffer bb, int end, int parentWidth, int packedType) {
+        Reference(ReadBuf bb, int end, int parentWidth, int packedType) {
             this(bb, end, parentWidth, (1 << (packedType & 3)), packedType >> 2);
         }
 
-        Reference(ByteBuffer bb, int end, int parentWidth, int byteWidth, int type) {
+        Reference(ReadBuf bb, int end, int parentWidth, int byteWidth, int type) {
             this.bb = bb;
             this.end = end;
             this.parentWidth = parentWidth;
@@ -484,13 +495,13 @@
             if (isString()) {
                 int start = indirect(bb, end, parentWidth);
                 int size = (int) readUInt(bb, start - byteWidth, byteWidth);
-                return Utf8.getDefault().decodeUtf8(bb, start, size);
+                return bb.getString(start, size);
             }
             else if (isKey()){
                 int start = indirect(bb, end, byteWidth);
                 for (int i = start; ; i++) {
                     if (bb.get(i) == 0) {
-                        return Utf8.getDefault().decodeUtf8(bb, start, i - start);
+                        return bb.getString(start, i - start);
                     }
                 }
             } else {
@@ -619,11 +630,11 @@
      * Points into the data buffer and allows access to one type.
      */
     private static abstract class Object {
-        ByteBuffer bb;
+        ReadBuf bb;
         int end;
         int byteWidth;
 
-        Object(ByteBuffer buff, int end, int byteWidth) {
+        Object(ReadBuf buff, int end, int byteWidth) {
             this.bb = buff;
             this.end = end;
             this.byteWidth = byteWidth;
@@ -640,9 +651,9 @@
     // Stores size in `byte_width_` bytes before end position.
     private static abstract class Sized extends Object {
 
-        private final int size;
+        protected final int size;
 
-        Sized(ByteBuffer buff, int end, int byteWidth) {
+        Sized(ReadBuf buff, int end, int byteWidth) {
             super(buff, end, byteWidth);
             size = readInt(bb, end - byteWidth, byteWidth);
         }
@@ -655,14 +666,14 @@
     /**
      * Represents a array of bytes element in the buffer
      *
-     * <p>It can be converted to `ByteBuffer` using {@link data()},
+     * <p>It can be converted to `ReadBuf` using {@link data()},
      * copied into a byte[] using {@link getBytes()} or
      * have individual bytes accessed individually using {@link get(int)}</p>
      */
     public static class Blob extends Sized {
         static final Blob EMPTY = new Blob(EMPTY_BB, 1, 1);
 
-        Blob(ByteBuffer buff, int end, int byteWidth) {
+        Blob(ReadBuf buff, int end, int byteWidth) {
             super(buff, end, byteWidth);
         }
 
@@ -672,11 +683,11 @@
         }
 
         /**
-         * Return {@link Blob} as `ByteBuffer`
-         * @return blob as `ByteBuffer`
+         * Return {@link Blob} as `ReadBuf`
+         * @return blob as `ReadBuf`
          */
         public ByteBuffer data() {
-            ByteBuffer dup = bb.duplicate();
+            ByteBuffer dup = ByteBuffer.wrap(bb.data());
             dup.position(end);
             dup.limit(end + size());
             return dup.asReadOnlyBuffer().slice();
@@ -709,7 +720,7 @@
          */
         @Override
         public String toString() {
-            return Utf8.getDefault().decodeUtf8(bb, end, size());
+            return bb.getString(end, size());
         }
 
         /**
@@ -718,7 +729,7 @@
         @Override
         public StringBuilder toString(StringBuilder sb) {
             sb.append('"');
-            sb.append(Utf8.getDefault().decodeUtf8(bb, end, size()));
+            sb.append(bb.getString(end, size()));
             return sb.append('"');
         }
     }
@@ -731,7 +742,7 @@
 
         private static final Key EMPTY = new Key(EMPTY_BB, 0, 0);
 
-        Key(ByteBuffer buff, int end, int byteWidth) {
+        Key(ReadBuf buff, int end, int byteWidth) {
             super(buff, end, byteWidth);
         }
 
@@ -760,7 +771,7 @@
                     break;
                 }
             }
-            return Utf8.getDefault().decodeUtf8(bb, end, size);
+            return bb.getString(end, size);
         }
 
         int compareTo(byte[] other) {
@@ -808,7 +819,7 @@
     public static class Map extends Vector {
         private static final Map EMPTY_MAP = new Map(EMPTY_BB, 1, 1);
 
-        Map(ByteBuffer bb, int end, int byteWidth) {
+        Map(ReadBuf bb, int end, int byteWidth) {
             super(bb, end, byteWidth);
         }
 
@@ -913,7 +924,7 @@
 
         private static final Vector EMPTY_VECTOR = new Vector(EMPTY_BB, 1, 1);
 
-        Vector(ByteBuffer bb, int end, int byteWidth) {
+        Vector(ReadBuf bb, int end, int byteWidth) {
             super(bb, end, byteWidth);
         }
 
@@ -976,7 +987,7 @@
 
         private final int elemType;
 
-        TypedVector(ByteBuffer bb, int end, int byteWidth, int elemType) {
+        TypedVector(ReadBuf bb, int end, int byteWidth, int elemType) {
             super(bb, end, byteWidth);
             this.elemType = elemType;
         }
diff --git a/java/com/google/flatbuffers/FlexBuffersBuilder.java b/java/com/google/flatbuffers/FlexBuffersBuilder.java
index c8438bb..cb44492 100644
--- a/java/com/google/flatbuffers/FlexBuffersBuilder.java
+++ b/java/com/google/flatbuffers/FlexBuffersBuilder.java
@@ -83,7 +83,7 @@
     private static final int WIDTH_16 = 1;
     private static final int WIDTH_32 = 2;
     private static final int WIDTH_64 = 3;
-    private final ByteBuffer bb;
+    private final ReadWriteBuf bb;
     private final ArrayList<Value> stack = new ArrayList<>();
     private final HashMap<String, Integer> keyPool = new HashMap<>();
     private final HashMap<String, Integer> stringPool = new HashMap<>();
@@ -116,7 +116,7 @@
      * @param bufSize size of buffer in bytes.
      */
     public FlexBuffersBuilder(int bufSize) {
-        this(ByteBuffer.allocate(bufSize), BUILDER_FLAG_SHARE_KEYS);
+        this(new ArrayReadWriteBuf(bufSize), BUILDER_FLAG_SHARE_KEYS);
     }
 
     /**
@@ -132,11 +132,14 @@
      * @param bb    `ByteBuffer` that will hold the message
      * @param flags Share flags
      */
+    @Deprecated
     public FlexBuffersBuilder(ByteBuffer bb, int flags) {
+        this(new ArrayReadWriteBuf(bb.array()), flags);
+    }
+
+    public FlexBuffersBuilder(ReadWriteBuf bb, int flags) {
         this.bb = bb;
         this.flags = flags;
-        bb.order(ByteOrder.LITTLE_ENDIAN);
-        bb.position(0);
     }
 
     /**
@@ -154,7 +157,7 @@
      *
      * @return `ByteBuffer` with finished message
      */
-    public ByteBuffer getBuffer() {
+    public ReadWriteBuf getBuffer() {
         assert (finished);
         return bb;
     }
@@ -180,17 +183,20 @@
         if (key == null) {
             return -1;
         }
-        int pos = bb.position();
+        int pos = bb.writePosition();
         if ((flags & BUILDER_FLAG_SHARE_KEYS) != 0) {
-            if (keyPool.get(key) == null) {
-                bb.put(key.getBytes(StandardCharsets.UTF_8));
+            Integer keyFromPool = keyPool.get(key);
+            if (keyFromPool == null) {
+                byte[]  keyBytes = key.getBytes(StandardCharsets.UTF_8);
+                bb.put(keyBytes, 0, keyBytes.length);
                 bb.put((byte) 0);
                 keyPool.put(key, pos);
             } else {
-                pos = keyPool.get(key);
+                pos = keyFromPool;
             }
         } else {
-            bb.put(key.getBytes(StandardCharsets.UTF_8));
+            byte[]  keyBytes = key.getBytes(StandardCharsets.UTF_8);
+            bb.put(keyBytes, 0, keyBytes.length);
             bb.put((byte) 0);
             keyPool.put(key, pos);
         }
@@ -373,8 +379,8 @@
         int bitWidth = widthUInBits(blob.length);
         int byteWidth = align(bitWidth);
         writeInt(blob.length, byteWidth);
-        int sloc = bb.position();
-        bb.put(blob);
+        int sloc = bb.writePosition();
+        bb.put(blob, 0, blob.length);
         if (trailing) {
             bb.put((byte) 0);
         }
@@ -384,7 +390,7 @@
     // Align to prepare for writing a scalar with a certain size.
     private int align(int alignment) {
         int byteWidth = 1 << alignment;
-        int padBytes = Value.paddingBytes(bb.position(), byteWidth);
+        int padBytes = Value.paddingBytes(bb.writePosition(), byteWidth);
         while (padBytes-- != 0) {
             bb.put((byte) 0);
         }
@@ -463,15 +469,14 @@
         // some other object.
         assert (stack.size() == 1);
         // Write root value.
-        int byteWidth = align(stack.get(0).elemWidth(bb.position(), 0));
+        int byteWidth = align(stack.get(0).elemWidth(bb.writePosition(), 0));
         writeAny(stack.get(0), byteWidth);
         // Write root type.
         bb.put(stack.get(0).storedPackedType());
         // Write root size. Normally determined by parent, but root has no parent :)
         bb.put((byte) byteWidth);
-        bb.limit(bb.position());
         this.finished = true;
-        return bb;
+        return ByteBuffer.wrap(bb.data(), 0, bb.writePosition());
     }
 
     /*
@@ -493,13 +498,13 @@
         if (keys != null) {
             // If this vector is part of a map, we will pre-fix an offset to the keys
             // to this vector.
-            bitWidth = Math.max(bitWidth, keys.elemWidth(bb.position(), 0));
+            bitWidth = Math.max(bitWidth, keys.elemWidth(bb.writePosition(), 0));
             prefixElems += 2;
         }
         int vectorType = FBT_KEY;
         // Check bit widths and types for all elements.
         for (int i = start; i < stack.size(); i++) {
-            int elemWidth = stack.get(i).elemWidth(bb.position(), i + prefixElems);
+            int elemWidth = stack.get(i).elemWidth(bb.writePosition(), i + prefixElems);
             bitWidth = Math.max(bitWidth, elemWidth);
             if (typed) {
                 if (i == start) {
@@ -528,7 +533,7 @@
             writeInt(length, byteWidth);
         }
         // Then the actual data.
-        int vloc = bb.position();
+        int vloc = bb.writePosition();
         for (int i = start; i < stack.size(); i++) {
             writeAny(stack.get(i), byteWidth);
         }
@@ -544,7 +549,7 @@
     }
 
     private void writeOffset(long val, int byteWidth) {
-        int reloff = (int) (bb.position() - val);
+        int reloff = (int) (bb.writePosition() - val);
         assert (byteWidth == 8 || reloff < 1L << (byteWidth * 8));
         writeInt(reloff, byteWidth);
     }
@@ -610,7 +615,7 @@
         int prefixElems = 1;
         // Check bit widths and types for all elements.
         for (int i = start; i < stack.size(); i++) {
-            int elemWidth = Value.elemWidth(FBT_KEY, WIDTH_8, stack.get(i).key, bb.position(), i + prefixElems);
+            int elemWidth = Value.elemWidth(FBT_KEY, WIDTH_8, stack.get(i).key, bb.writePosition(), i + prefixElems);
             bitWidth = Math.max(bitWidth, elemWidth);
         }
 
@@ -618,7 +623,7 @@
         // Write vector. First the keys width/offset if available, and size.
         writeInt(length, byteWidth);
         // Then the actual data.
-        int vloc = bb.position();
+        int vloc = bb.writePosition();
         for (int i = start; i < stack.size(); i++) {
             int pos = stack.get(i).key;
             assert(pos != -1);
diff --git a/java/com/google/flatbuffers/ReadBuf.java b/java/com/google/flatbuffers/ReadBuf.java
new file mode 100644
index 0000000..dbb4e73
--- /dev/null
+++ b/java/com/google/flatbuffers/ReadBuf.java
@@ -0,0 +1,81 @@
+package com.google.flatbuffers;
+
+/**
+ *  Represent a chunk of data, where FlexBuffers will read from.
+ */
+interface ReadBuf {
+
+  /**
+   * Read boolean from data. Booleans as stored as single byte
+   * @param index position of the element in ReadBuf
+   * @return boolean element
+   */
+  boolean getBoolean(int index);
+
+  /**
+   * Read a byte from data.
+   * @param index position of the element in ReadBuf
+   * @return a byte
+   */
+  byte get(int index);
+
+  /**
+   * Read a short from data.
+   * @param index position of the element in ReadBuf
+   * @return a short
+   */
+  short getShort(int index);
+
+  /**
+   * Read a 32-bit int from data.
+   * @param index position of the element in ReadBuf
+   * @return an int
+   */
+  int getInt(int index);
+
+  /**
+   * Read a 64-bit long from data.
+   * @param index position of the element in ReadBuf
+   * @return a long
+   */
+  long getLong(int index);
+
+  /**
+   * Read a 32-bit float from data.
+   * @param index position of the element in ReadBuf
+   * @return a float
+   */
+  float getFloat(int index);
+
+  /**
+   * Read a 64-bit float from data.
+   * @param index position of the element in ReadBuf
+   * @return a double
+   */
+  double getDouble(int index);
+
+  /**
+   * Read an UTF-8 string from data.
+   * @param start initial element of the string
+   * @param size size of the string in bytes.
+   * @return a {@code String}
+   */
+  String getString(int start, int size);
+
+  /**
+   * Expose ReadBuf as an array of bytes.
+   * This method is meant to be as efficient as possible, so for a array-backed ReadBuf, it should
+   * return its own internal data. In case access to internal data is not possible,
+   * a copy of the data into an array of bytes might occur.
+   * @return ReadBuf as an array of bytes
+   */
+  byte[] data();
+
+  /**
+   * Defines the size of the message in the buffer. It also determines last position that buffer
+   * can be read. Last byte to be accessed is in position {@code limit() -1}.
+   * @return indicate last position
+   */
+  int limit();
+
+}
diff --git a/java/com/google/flatbuffers/ReadWriteBuf.java b/java/com/google/flatbuffers/ReadWriteBuf.java
new file mode 100644
index 0000000..df672fb
--- /dev/null
+++ b/java/com/google/flatbuffers/ReadWriteBuf.java
@@ -0,0 +1,135 @@
+package com.google.flatbuffers;
+
+/**
+ * Interface to represent a read-write buffer. This interface will be used to access and write
+ * FlexBuffers message.
+ */
+interface ReadWriteBuf extends ReadBuf {
+    /**
+     * Put a boolean into the buffer at {@code writePosition()} . Booleans as stored as single
+     * byte. Write position will be incremented.
+     * @return boolean element
+     */
+    void putBoolean(boolean value);
+
+    /**
+     * Put an array of bytes into the buffer at {@code writePosition()}. Write position will be
+     * incremented.
+     * @param value the data to be copied
+     * @param start initial position on value to be copied
+     * @param length amount of bytes to be copied
+     */
+    void put (byte[] value, int start, int length);
+
+    /**
+     * Write a byte into the buffer at {@code writePosition()}. Write position will be
+     * incremented.
+     */
+    void put(byte value);
+
+    /**
+     * Write a 16-bit into in the buffer at {@code writePosition()}. Write position will be
+     * incremented.
+     */
+    void putShort(short value);
+
+    /**
+     * Write a 32-bit into in the buffer at {@code writePosition()}. Write position will be
+     * incremented.
+     */
+    void putInt(int value);
+
+    /**
+     * Write a 64-bit into in the buffer at {@code writePosition()}. Write position will be
+     * incremented.
+     */
+    void putLong(long value);
+
+    /**
+     * Write a 32-bit float into the buffer at {@code writePosition()}. Write position will be
+     * incremented.
+     */
+    void putFloat(float value);
+
+    /**
+     * Write a 64-bit float into the buffer at {@code writePosition()}. Write position will be
+     * incremented.
+     */
+    void putDouble(double value);
+
+    /**
+     * Write boolean into a given position on the buffer. Booleans as stored as single byte.
+     * @param index position of the element in buffer
+     */
+    void setBoolean(int index, boolean value);
+
+    /**
+     * Read a byte from data.
+     * @param index position of the element in the buffer
+     * @return a byte
+     */
+    void set(int index, byte value);
+
+    /**
+     * Write an array of bytes into the buffer.
+     * @param index initial position of the buffer to be written
+     * @param value the data to be copied
+     * @param start initial position on value to be copied
+     * @param length amount of bytes to be copied
+     */
+    void set(int index, byte[] value, int start, int length);
+
+    /**
+     * Read a short from data.
+     * @param index position of the element in ReadBuf
+     * @return a short
+     */
+    void setShort(int index, short value);
+
+    /**
+     * Read a 32-bit int from data.
+     * @param index position of the element in ReadBuf
+     * @return an int
+     */
+    void setInt(int index, int value);
+
+    /**
+     * Read a 64-bit long from data.
+     * @param index position of the element in ReadBuf
+     * @return a long
+     */
+    void setLong(int index, long value);
+
+    /**
+     * Read a 32-bit float from data.
+     * @param index position of the element in ReadBuf
+     * @return a float
+     */
+    void setFloat(int index, float value);
+
+    /**
+     * Read a 64-bit float from data.
+     * @param index position of the element in ReadBuf
+     * @return a double
+     */
+    void setDouble(int index, double value);
+
+
+    int writePosition();
+    /**
+     * Defines the size of the message in the buffer. It also determines last position that buffer
+     * can be read or write. Last byte to be accessed is in position {@code limit() -1}.
+     * @return indicate last position
+     */
+    int limit();
+
+    /**
+     * Request capacity of the buffer. In case buffer is already larger
+     * than the requested, this method will just return true. Otherwise
+     * It might try to resize the buffer.
+     *
+     * @return true if buffer is able to offer
+     * the requested capacity
+     */
+    boolean requestCapacity(int capacity);
+}
diff --git a/java/com/google/flatbuffers/Utf8Safe.java b/java/com/google/flatbuffers/Utf8Safe.java
index 06ea420..523e3f1 100644
--- a/java/com/google/flatbuffers/Utf8Safe.java
+++ b/java/com/google/flatbuffers/Utf8Safe.java
@@ -123,7 +123,7 @@
     return utf8Length;
   }
 
-  private static String decodeUtf8Array(byte[] bytes, int index, int size) {
+  public static String decodeUtf8Array(byte[] bytes, int index, int size) {
     // Bitwise OR combines the sign bits so any negative value fails the check.
     if ((index | size | bytes.length - index - size) < 0) {
       throw new ArrayIndexOutOfBoundsException(
@@ -197,7 +197,7 @@
     return new String(resultArr, 0, resultPos);
   }
 
-  private static String decodeUtf8Buffer(ByteBuffer buffer, int offset,
+  public static String decodeUtf8Buffer(ByteBuffer buffer, int offset,
                                          int length) {
     // Bitwise OR combines the sign bits so any negative value fails the check.
     if ((offset | length | buffer.limit() - offset - length) < 0) {
diff --git a/tests/JavaTest.java b/tests/JavaTest.java
index 766d352..5ea9baa 100644
--- a/tests/JavaTest.java
+++ b/tests/JavaTest.java
@@ -15,6 +15,7 @@
 import com.google.flatbuffers.FlexBuffers.FlexBufferException;
 import com.google.flatbuffers.FlexBuffers.Reference;
 import com.google.flatbuffers.FlexBuffers.Vector;
+import com.google.flatbuffers.ArrayReadWriteBuf;
 
 import java.io.*;
 import java.math.BigInteger;
@@ -1007,6 +1008,23 @@
         TestEq(source, result);
     }
 
+    public static void testBuilderGrowth() {
+        FlexBuffersBuilder builder = new FlexBuffersBuilder();
+        builder.putString("This is a small string");
+        ByteBuffer b = builder.finish();
+        TestEq("This is a small string", FlexBuffers.getRoot(b).asString());
+
+        FlexBuffersBuilder failBuilder = new FlexBuffersBuilder(ByteBuffer.allocate(1));
+        try {
+            failBuilder.putString("This is a small string");
+            // This should never be reached, it should throw an exception
+            // since ByteBuffers do not grow
+            assert(false);
+        } catch (java.lang.ArrayIndexOutOfBoundsException exception) {
+            // It should throw exception
+        }
+    }
+
     public static void TestFlexBuffers() {
         testSingleElementByte();
         testSingleElementShort();
@@ -1028,6 +1046,7 @@
         testFlexBuferEmpty();
         testFlexBufferVectorStrings();
         testDeprecatedTypedVectorString();
+        testBuilderGrowth();
     }
 
     static void TestVectorOfBytes() {