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:
- Path components require
%20(the+in a path means literal plus) - OAuth, AWS signing, and most modern APIs require strict RFC 3986
- It’s the cross-language standard
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 value | URLEncoder.encode(v, UTF_8) |
| Encode a path segment | URLEncoder.encode(v, UTF_8).replace("+", "%20") |
| Build a URL from parts | new URI(scheme, host, path, query, fragment) |
| Modern HTTP client | URI.create(...) + HttpRequest |
| Spring app | UriComponentsBuilder |
| Apache HttpClient | URIBuilder |
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.