code: @Nullable
    public static String readSafeUTF (@Nonnull final DataInput aDI) throws IOException
    {
      ValueEnforcer.notNull (aDI, "DataInput");
  
      final int nLayout = aDI.readByte ();
      final String ret;
      switch (nLayout)
      {
        case 0:
        {
          // If the first byte has value "0" it means the whole String is simply null
          ret = null;
          break;
        }
        case 1:
        {
          // length in UTF-8 bytes followed by the main bytes
          final int nLength = aDI.readInt ();
          final byte [] aData = new byte [nLength];
          aDI.readFully (aData);
          ret = new String (aData, StandardCharsets.UTF_8);
          break;
        }
        case 2:
        {
          // length in UTF-8 bytes followed by the main bytes, than the end of byte marker
          final int nLength = aDI.readInt ();
          final byte [] aData = new byte [nLength];
          aDI.readFully (aData);
          ret = new String (aData, StandardCharsets.UTF_8);
  
          final int nEndOfString = aDI.readInt ();
          if (nEndOfString != END_OF_STRING_MARKER)
            throw new IOException ("Missing end of String marker");
          break;
        }
        default:
          throw new IOException ("Unsupported string layout version " + nLayout);
      }
  
      return ret;
    }
docs: Because {@link DataOutputStream#writeUTF(String)} has a limit of 64KB this
  methods provides a similar solution for reading like
  {@link DataInputStream#readUTF()} but what was written in
  {@link #writeSafeUTF(DataOutput, String)}.
  
  @param aDI
  {@link DataInput} to read from. May not be <code>null</code>.
  @return The read string. May be <code>null</code>.
  @throws IOException
  on read error
  @see #writeSafeUTF(DataOutput, String)