Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Java has no standard-library method specified as an exact equivalent of JavaScript’s encodeURIComponent(). For matching output, encode the input’s UTF-8 bytes, leave only JavaScript’s permitted characters unescaped, and percent-encode the rest. URLEncoder is for form encoding, where spaces become +, so it is not a drop-in replacement.

A Java implementation that matches JavaScript

This method matches encodeURIComponent() for valid Java strings: it uses UTF-8, uppercase hexadecimal escapes, and the same unescaped character set. It also rejects unpaired UTF-16 surrogates, which JavaScript reports as a URIError rather than silently replacing.

import java.nio.charset.StandardCharsets;

public final class JavaScriptUriEncoding {
    private static final char[] HEX = "0123456789ABCDEF".toCharArray();

    private JavaScriptUriEncoding() {
    }

    public static String encodeURIComponent(String input) {
        if (input == null) {
            throw new NullPointerException("input");
        }

        validateUtf16(input);
        byte[] bytes = input.getBytes(StandardCharsets.UTF_8);
        StringBuilder result = new StringBuilder(bytes.length);

        for (byte value : bytes) {
            int b = value & 0xFF;
            if (isSafe(b)) {
                result.append((char) b);
            } else {
                result.append('%')
                      .append(HEX[b >>> 4])
                      .append(HEX[b & 0x0F]);
            }
        }
        return result.toString();
    }

    private static boolean isSafe(int b) {
        return (b >= 'A' && b <= 'Z')
            || (b >= 'a' && b <= 'z')
            || (b >= '0' && b <= '9')
            || b == '-' || b == '_' || b == '.'
            || b == '!' || b == '~' || b == '*'
            || b == ''' || b == '(' || b == ')';
    }

    private static void validateUtf16(String input) {
        for (int i = 0; i < input.length(); i++) {
            char c = input.charAt(i);
            if (Character.isHighSurrogate(c)) {
                if (i + 1 >= input.length()
                        || !Character.isLowSurrogate(input.charAt(i + 1))) {
                    throw new IllegalArgumentException(
                        "Input contains a lone high surrogate at index " + i);
                }
                i++; // Consume the matching low surrogate.
            } else if (Character.isLowSurrogate(c)) {
                throw new IllegalArgumentException(
                    "Input contains a lone low surrogate at index " + i);
            }
        }
    }
}

The allowlist is A-Z, a-z, 0-9, and - _ . ! ~ * ' ( ). Every other character is first represented as UTF-8 bytes, then each byte is written as % followed by two uppercase hexadecimal digits. This is the character set and encoding behavior documented for JavaScript’s encodeURIComponent().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For example:

String encoded = JavaScriptUriEncoding.encodeURIComponent("A B&日本語/?.!~*'()");
System.out.println(encoded);
// A%20B%26%E6%97%A5%E6%9C%AC%E8%AA%9E%2F%3F.!~*'()

Spaces become %20; delimiters such as &, =, /, ?, and # are escaped. Japanese characters are encoded as their UTF-8 bytes. The punctuation !~*'() remains unescaped.

Why URLEncoder differs

URLEncoder implements application/x-www-form-urlencoded, the format used by HTML form data and some HTTP APIs. It is not a generic URI-component encoder. In form encoding, a space is represented by +.

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

String value = "a b+c&d";
System.out.println(URLEncoder.encode(value, StandardCharsets.UTF_8));
// a+b%2Bc%26d

JavaScript produces a%20b%2Bc%26d for the same input. The plus sign in the original value is escaped in both results; the difference is how a space is represented.

Use URLEncoder.encode(value, StandardCharsets.UTF_8) when the receiver expects form encoding. The Charset overload is available since Java 10. For JavaScript-compatible component output, use the explicit encoder above. Replacing + with %20 after form encoding may suffice for controlled, well-formed values, but it does not address malformed-surrogate behavior and disguises the distinction between two formats.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Unicode and malformed UTF-16

Java and JavaScript strings use UTF-16 code units. A character outside the Basic Multilingual Plane, such as 😀, is represented by a valid surrogate pair. Its UTF-8 bytes are F0 9F 98 80, so its encoded form is %F0%9F%98%80.

An unpaired high or low surrogate is different: JavaScript’s encoder throws for it. Java’s ordinary getBytes(UTF_8) conversion can replace malformed input, so validating the string before conversion is necessary for this behavior. The implementation throws IllegalArgumentException for these inputs; Java does not have JavaScript’s URIError type. See MDN’s explanation of malformed URI sequences.

This method accepts a Java String; it does not reproduce JavaScript’s dynamic argument coercion. JavaScript stringifies values such as numbers, booleans, null, and undefined before encoding. A Java method with a String parameter does not do that, and this implementation deliberately rejects null. If an application needs coercion, define it explicitly at the call site rather than assuming the encoder provides it.

Encode component values, not query syntax

encodeURIComponent() is intended for a component value, not for a complete URL. Encode each query value separately, then assemble the delimiters:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
String query = "name="
    + JavaScriptUriEncoding.encodeURIComponent("Jack & Jill")
    + "&city="
    + JavaScriptUriEncoding.encodeURIComponent("Boston");

System.out.println(query);
// name=Jack%20%26%20Jill&city=Boston

The ampersand inside the name becomes %26, while the ampersand separating parameters stays literal. Encoding the complete query as one value would escape its separators; leaving a value unencoded can let characters such as & or = alter how it is parsed.

For building a complete URI, use a URI or framework builder that understands its components and verify the escaping rules for the specific builder and component. A path segment, query value, and whole URI do not necessarily use the same rules. java.net.URI models and parses URI structure; it is not a drop-in encodeURIComponent() function.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Do not confuse JavaScript output with stricter RFC 3986 escaping

JavaScript leaves ! ' ( ) * unescaped. Some applications instead require a stricter RFC 3986 component representation that escapes those five characters as %21, %27, %28, %29, and %2A. That is a different output target; do not change those characters if compatibility with JavaScript is the requirement. MDN describes this distinction in its reference.

Test the cases that reveal mismatches

A useful parity test covers spaces, reserved characters, multibyte text, emoji, the safe punctuation, and malformed surrogate inputs—not just plain ASCII.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;

class JavaScriptUriEncodingTest {
    @Test
    void matchesJavaScriptForMixedInput() {
        assertEquals(
            "A%20B%26%E6%97%A5%E6%9C%AC%E8%AA%9E%2F%3F.!~*'()",
            JavaScriptUriEncoding.encodeURIComponent("A B&日本語/?.!~*'()"));
    }

    @Test
    void encodesPlusAndEmoji() {
        assertEquals("%2B", JavaScriptUriEncoding.encodeURIComponent("+"));
        assertEquals("%F0%9F%98%80", JavaScriptUriEncoding.encodeURIComponent("😀"));
    }

    @Test
    void rejectsUnpairedSurrogates() {
        assertThrows(IllegalArgumentException.class,
            () -> JavaScriptUriEncoding.encodeURIComponent("uD800"));
        assertThrows(IllegalArgumentException.class,
            () -> JavaScriptUriEncoding.encodeURIComponent("uDFFF"));
    }
}

Additional expected results include hello world → hello%20world, a&b=c → a%26b%3Dc, /path?x=1#top → %2Fpath%3Fx%3D1%23top, é → %C3%A9, and !~*'() → !~*'().

Decoding is a separate choice

JavaScript’s decodeURIComponent("+") returns a literal plus. Java’s URLDecoder is for form-encoded data and turns + into a space, so it is not an exact counterpart. It pairs with URLEncoder, not with JavaScript’s component encoder. If you need exact decoding parity, use a decoder designed and tested for that requirement rather than substituting URLDecoder.

Choose by the required wire format

Requirement Use
Match JavaScript encodeURIComponent() The explicit UTF-8 component encoder above
Encode HTML form data URLEncoder with UTF-8
Decode form data URLDecoder with UTF-8
Assemble a complete URI A URI or framework builder, checked for the relevant component’s rules
Strict RFC 3986 component output An encoder intentionally implementing that target, not JavaScript compatibility

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.