code: public static String uriDecode(final String source, final Charset charset) {
      final int length = source.length();
      if (length == 0) {
        return source;
      }
      Assert.notNull(charset, "Charset must not be null");
  
      final ByteArrayOutputStream bos = new ByteArrayOutputStream(length);
      boolean changed = false;
      for (int i = 0; i < length; i++) {
        final int ch = source.charAt(i);
        if (ch == '%') {
          if (i + 2 < length) {
            final char hex1 = source.charAt(i + 1);
            final char hex2 = source.charAt(i + 2);
            final int u = Character.digit(hex1, 16);
            final int l = Character.digit(hex2, 16);
            if (u == -1 || l == -1) {
              throw new IllegalArgumentException(
                  "Invalid encoded sequence \"" + source.substring(i) + "\"");
            }
            bos.write((char) ((u << 4) + l));
            i += 2;
            changed = true;
          } else {
            throw new IllegalArgumentException(
                "Invalid encoded sequence \"" + source.substring(i) + "\"");
          }
        } else {
          bos.write(ch);
        }
      }
      return changed ? new String(bos.toByteArray(), charset) : source;
    }
docs: Decode the given encoded URI component value. Based on the following rules:
  <ul>
  <li>Alphanumeric characters {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"},
  and {@code "0"} through {@code "9"} stay the same.</li>
  <li>Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the
  same.</li>
  <li>A sequence "{@code %<i>xy</i>}" is interpreted as a hexadecimal representation of the
  character.</li>
  </ul>
  
  @param source the encoded String
  @param charset the character set
  @return the decoded value
  @throws IllegalArgumentException when the given source contains invalid encoded sequences
  @since 5.0
  @see java.net.URLDecoder#decode(String, String)