Article · Apr 20, 2026 · 6 min read

URL encoding in Java

Java’s URL encoding situation is uniquely annoying. The built-in URLEncoder has historical quirks, the URI and URL classes interact in confusing ways, and there’s a recurring trap that makes spaces in URLs come out as + when you wanted %20. This article covers what works, what doesn’t, and how to write modern Java that produces correct URLs.

The basic functions

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

String encoded = URLEncoder.encode("Hello, World!", StandardCharsets.UTF_8);
// "Hello%2C+World%21"

String decoded = URLDecoder.decode("Hello%2C+World%21", StandardCharsets.UTF_8);
// "Hello, World!"

Note the result: space became +, not %20. URLEncoder implements the form-encoded variant (application/x-www-form-urlencoded), not RFC 3986. This is the source of most Java URL encoding confusion.

The + problem

Java’s URLEncoder was designed for form submission, where + for space is correct. But most modern code building URLs wants RFC 3986 (space → %20), because:

The standard workaround:

String rfc3986(String value) {
    return URLEncoder.encode(value, StandardCharsets.UTF_8)
        .replace("+", "%20")
        .replace("*", "%2A")
        .replace("%7E", "~");
}

The * and ~ fixups bring it fully in line with RFC 3986 — URLEncoder leaves * unencoded (RFC says encode it) and encodes ~ (RFC says leave it).

Using URI for paths

For building paths, the java.net.URI class encodes correctly:

import java.net.URI;
import java.net.URISyntaxException;

URI uri = new URI("https", "example.com", "/products/My Item", null, null);
String url = uri.toASCIIString();
// "https://example.com/products/My%20Item"

The 5-argument constructor splits the URL into scheme, authority, path, query, fragment. toASCIIString() handles all the encoding. Note that it does NOT encode forward slashes in the path (correct — they’re path separators).

If you have a path with a literal slash that needs encoding (e.g., product name like "A/B Testing"), you have to encode that one segment yourself before constructing the URI:

String name = "A/B Testing";
String encodedName = URLEncoder.encode(name, StandardCharsets.UTF_8)
    .replace("+", "%20");
URI uri = new URI("https://example.com/products/" + encodedName);
// "https://example.com/products/A%2FB+Testing" — wait, the + got through

// Better approach:
String safeSegment = encodedName.replace("+", "%20");
URI uri2 = new URI("https://example.com/products/" + safeSegment);

Building query strings

Java has no built-in query-string builder analogous to Python’s urlencode or PHP’s http_build_query. The idiomatic approach is to build it manually with a StringJoiner or stream:

import java.util.Map;
import java.util.stream.Collectors;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

Map<String, String> params = Map.of(
    "q", "Hello, World!",
    "lang", "en"
);

String query = params.entrySet().stream()
    .map(e -> URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8)
        + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
    .collect(Collectors.joining("&"));
// "q=Hello%2C+World%21&lang=en"  (or whatever order Map.of gives)

For ordered or repeated keys, use LinkedHashMap or a List<Map.Entry<String, String>>.

The HttpClient approach (modern Java 11+)

Since Java 11, HttpRequest can build URIs cleanly:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

String query = "q=" + URLEncoder.encode("Hello, World!", StandardCharsets.UTF_8);
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/search?" + query))
    .GET()
    .build();

You still need to encode parameter values manually; the HttpClient doesn’t do it for you.

Apache HttpClient and URIBuilder

If you use the Apache HttpClient library (very common in enterprise Java), it has a URIBuilder that handles encoding properly:

import org.apache.http.client.utils.URIBuilder;

URI uri = new URIBuilder()
    .setScheme("https")
    .setHost("api.example.com")
    .setPath("/search")
    .addParameter("q", "Hello, World!")
    .addParameter("lang", "en")
    .build();
// https://api.example.com/search?q=Hello%2C+World%21&lang=en

Note: even URIBuilder uses + for spaces in query strings, because the form-encoded convention is the legacy default. Spring’s UriComponentsBuilder behaves similarly.

Spring’s UriComponentsBuilder

If you’re in a Spring app:

import org.springframework.web.util.UriComponentsBuilder;

String uri = UriComponentsBuilder.fromHttpUrl("https://api.example.com/search")
    .queryParam("q", "Hello, World!")
    .queryParam("lang", "en")
    .encode()                  // ← important — encode before .build()
    .build()
    .toUriString();
// https://api.example.com/search?q=Hello,%20World!&lang=en

Spring’s builder is unusual — it produces RFC 3986 encoding (%20 for space) by default with .encode(). Without .encode(), you get the literal values, which is wrong for non-ASCII data.

Common Java URL encoding mistakes

Mistake 1: Using URLEncoder.encode(value) without the charset

Before Java 10, there was a single-argument overload that used the platform default charset. It’s deprecated for good reason — on Windows-default systems it could produce different output than Linux. Always pass the charset:

URLEncoder.encode(value, StandardCharsets.UTF_8);  // correct
URLEncoder.encode(value);                          // deprecated — don’t

Mistake 2: Encoding a full URL with URLEncoder.encode

// Wrong
String result = URLEncoder.encode("https://example.com/page?q=hello", StandardCharsets.UTF_8);
// "https%3A%2F%2Fexample.com%2Fpage%3Fq%3Dhello"
// That’s no longer a URL — it’s an encoded blob.

// Right — only encode the values
String base = "https://example.com/page";
String q = URLEncoder.encode("hello", StandardCharsets.UTF_8);
String result = base + "?q=" + q;

Mistake 3: Forgetting that URLDecoder treats + as space

// You have a path segment with a literal + (e.g., "C++")
String segment = "C%2B%2B+programming";
String decoded = URLDecoder.decode(segment, StandardCharsets.UTF_8);
// "C++ programming"  ← but you expected "C+++programming"

If your input is a path component (not a query value), the + shouldn’t become space. Either pre-process to encode literal + as %2B before decoding, or use a more careful manual decoder.

Quick reference card

Goal Approach
Encode a query valueURLEncoder.encode(v, UTF_8)
Encode a path segmentURLEncoder.encode(v, UTF_8).replace("+", "%20")
Build a URL from partsnew URI(scheme, host, path, query, fragment)
Modern HTTP clientURI.create(...) + HttpRequest
Spring appUriComponentsBuilder
Apache HttpClientURIBuilder

The short version: URLEncoder.encode(v, UTF_8) for query values, replace + with %20 for path segments. If you’re in a framework (Spring, Jakarta EE, Quarkus), use its built-in builder — it handles the edge cases.


Found this useful? Try the URL decoder, the URL encoder, or browse all tools.

Frequently asked

Common questions.

Because without it, URLEncoder.encode falls back to the platform’s default charset, which varies by system and can corrupt multi-byte characters. Always pass UTF-8 explicitly: URLEncoder.encode(value, StandardCharsets.UTF_8).

URLEncoder follows the form-encoding convention, so it encodes a space as +, not %20. That’s correct for query values but wrong for path segments — for a path, use java.net.URI or replace + with %20 afterward.

Because it escapes the structural characters too — :, /, ? — turning https:// into https%3A%2F%2F, which is no longer a usable URL. Encode individual values and assemble the URL, or use a builder like UriComponentsBuilder.

Yes. URLDecoder.decode converts + to a space because it follows the form-encoding convention. If you’re decoding a path where + is a literal plus, that behaviour is wrong — account for it or decode with a path-aware approach.

Sources & standards

Honest scope: the Java API docs are authoritative for behaviour; note URLEncoder follows form-encoding (spaces as +), not RFC 3986 path rules.

More reading

From the blog.

About this page

Written and maintained by the urlencodedecode.com team. Every technical claim on this page is verified against primary sources — the RFCs (3986, 3629, 4648, 7578), the WHATWG URL Standard, and official vendor or language documentation — rather than second-hand summaries. When a source contradicts a common assumption, we follow the source and note the discrepancy. Corrections: contactus@urlencodedecode.com.