Skip to content

URL Encode / Decode — Java source

Percent-encode or decode URLs and query parameters. Choose component (encodeURIComponent) or full-URI (encodeURI) mode. 100% client-side.

This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.

// url-encode — percent encode/decode, component (encodeURIComponent) and full-URI (encodeURI) scope.
// Language: Java (17+, standard library only). Port of src/tools/UrlEncodeTool.tsx — same
// contract as this dir's go.go: each byte outside the safe set becomes %XX (uppercase hex);
// component keeps A-Za-z0-9-_.!~*'(), full URI also keeps ;,/?:@&=+$#; decode is strict (bad %
// pair, invalid UTF-8 -> error) and full-URI scope leaves encoded reserved bytes intact.
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;

final class UrlEncode {

    record Result(String output, String error) {} // error == null on success
    static final String BAD = "malformed URI sequence";
    static final String HEX = "0123456789ABCDEF";

    static boolean compSafe(byte c) {
        return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
            || "-_.!~*'()".indexOf(c) >= 0;
    }

    static boolean uriExtra(byte c) { return ";,/?:@&=+$#".indexOf(c) >= 0; }

    static String encode(String s, boolean fullUri) {
        StringBuilder b = new StringBuilder(s.length() * 3);
        for (byte cb : s.getBytes(StandardCharsets.UTF_8)) {
            int c = cb & 0xff;
            if (compSafe(cb) || (fullUri && uriExtra(cb))) b.append((char) c);
            else b.append('%').append(HEX.charAt(c >> 4)).append(HEX.charAt(c & 0xf));
        }
        return b.toString();
    }

    static int unhex(char c) {
        if (c >= '0' && c <= '9') return c - '0';
        if (c >= 'a' && c <= 'f') return c - 'a' + 10;
        if (c >= 'A' && c <= 'F') return c - 'A' + 10;
        return -1;
    }

    static Result decode(String s, boolean fullUri) {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream(s.length());
        for (int i = 0; i < s.length();) {
            if (s.charAt(i) != '%') { bytes.write(s.charAt(i++)); continue; }
            if (i + 2 >= s.length()) return new Result("", BAD); // truncated tail
            int hi = unhex(s.charAt(i + 1)), lo = unhex(s.charAt(i + 2));
            if (hi < 0 || lo < 0) return new Result("", BAD); // bad hex digit
            int c = hi << 4 | lo;
            if (fullUri && c < 0x80 && uriExtra((byte) c)) { // decodeURI keeps encoded reserved
                bytes.write('%');
                bytes.write(s.charAt(i + 1));
                bytes.write(s.charAt(i + 2));
            } else {
                bytes.write(c);
            }
            i += 3;
        }
        try {
            // REPORT -> strict decoder: overlong/surrogate/truncated UTF-8 throws
            return new Result(StandardCharsets.UTF_8.newDecoder()
                .onMalformedInput(CodingErrorAction.REPORT)
                .onUnmappableCharacter(CodingErrorAction.REPORT)
                .decode(ByteBuffer.wrap(bytes.toByteArray())).toString(), null);
        } catch (CharacterCodingException e) {
            return new Result("", BAD);
        }
    }

    static void show(String label, Result r) { System.out.println(label + (r.error() != null ? r.error() : r.output())); }

    public static void main(String[] args) {
        System.out.println("enc:  " + encode("hello world & café", false));
        System.out.println("uri:  " + encode("hello world & café", true));
        show("dec:  ", decode("hello%20world%20%26%20caf%C3%A9", false));
        show("duri: ", decode("a%2Fb%3Fc%2Cd", true)); // reserved stay escaped in decodeURI
        show("dcmp: ", decode("a%2Fb%3Fc%2Cd", false)); // ...but decode in component scope
        show("bad:  ", decode("100%", false));
    }
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →