private void writeFullUTF(String str, boolean encodeLength) throws UTFDataFormatException {
    int strlen = str.length();
    if (encodeLength && strlen > 65535) {
      throw new UTFDataFormatException();
    }
    // make room for worst case space 3 bytes for each char and 2 for len
    {
      int maxLen = (strlen * 3);
      if (encodeLength) {
        maxLen += 2;
      }
      ensureCapacity(maxLen);
    }
    int utfSizeIdx = buffer.position();
    if (encodeLength) {
      // skip bytes reserved for length
      buffer.position(utfSizeIdx + 2);
    }
    for (int i = 0; i < strlen; i++) {
      int c = str.charAt(i);
      if ((c >= 0x0001) && (c <= 0x007F)) {
        buffer.put((byte) c);
      } else if (c > 0x07FF) {
        buffer.put((byte) (0xE0 | ((c >> 12) & 0x0F)));
        buffer.put((byte) (0x80 | ((c >> 6) & 0x3F)));
        buffer.put((byte) (0x80 | ((c >> 0) & 0x3F)));
      } else {
        buffer.put((byte) (0xC0 | ((c >> 6) & 0x1F)));
        buffer.put((byte) (0x80 | ((c >> 0) & 0x3F)));
      }
    }
    int utflen = buffer.position() - utfSizeIdx;
    if (encodeLength) {
      utflen -= 2;
      if (utflen > 65535) {
        // act as if we wrote nothing to this buffer
        buffer.position(utfSizeIdx);
        throw new UTFDataFormatException();
      }
      buffer.putShort(utfSizeIdx, (short) utflen);
    }
  }
The logic used here is based on java's DataOutputStream.writeUTF() from the version 1.6.0_10.
The reader code should use the logic similar to DataOutputStream.readUTF() from the version
1.6.0_10 to decode this properly.
geode-serialization/src/main/java/org/apache/geode/internal/serialization/BufferDataOutputStream.java