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.

More reading

From the blog.