Java URL Encoding — URLEncoder vs URI Class for Query Parameters
Java developers have a confusing choice when it comes to URL encoding.
The standard library provides:
java.net.URLEncoder— the older utility classjava.net.URI— the newer, more standards-compliant class
They do not produce the same output. They handle spaces, special characters, and encoding contexts differently. Picking the wrong one creates bugs that are hard to trace.
This guide covers both approaches with real examples, explains the differences, and provides production-tested patterns.
The Two Approaches
import java.net.URLEncoder;
import java.net.URI;
import java.net.URISyntaxException;
| Approach | Method | Standard |
|---|---|---|
| URLEncoder | URLEncoder.encode(input, "UTF-8") | application/x-www-form-urlencoded |
| URI class | new URI(scheme, authority, path, query, fragment) | RFC 3986 |
The difference matters more than most Java developers realize.
URLEncoder.encode() — The Legacy Approach
URLEncoder was designed for HTML form encoding, not URL encoding. It follows the application/x-www-form-urlencoded specification.
String encoded = URLEncoder.encode("hello world", "UTF-8");
System.out.println(encoded); // hello+world
Notice the space became +, not %20.
String encoded = URLEncoder.encode("node.js & react", "UTF-8");
System.out.println(encoded); // node.js+%26+react
URLEncoder encodes & but uses + for spaces.
What URLEncoder Encodes
| Character | Encoded |
|---|---|
| space | + |
& | %26 |
= | %3D |
? | %3F |
/ | not encoded by default in some versions |
This makes URLEncoder appropriate for form data but problematic for URL query parameters where %20 is the expected standard.
Common Bug with URLEncoder
String redirectUri = "https://myapp.com/callback?source=web";
String encoded = URLEncoder.encode(redirectUri, "UTF-8");
System.out.println(encoded);
Output:
https%3A%2F%2Fmyapp.com%2Fcallback%3Fsource%3Dweb
This looks correct — and it is for a query parameter value. But the + for spaces creates issues with services that expect %20.
The URI Class — The RFC 3986 Approach
The URI class follows the URL standard (RFC 3986). It provides multi-argument constructors that encode each component correctly.
URI uri = new URI(
"https",
"example.com",
"/search",
"q=hello world&category=node.js & react",
null
);
System.out.println(uri.toString());
Output:
https://example.com/search?q=hello%20world&category=node.js%20%26%20react
Notice:
- Spaces become
%20, not+ &is encoded as%26in the query- The URL structure is preserved
The URI constructor accepts five arguments:
new URI(scheme, authority, path, query, fragment)
Each component is encoded independently based on RFC 3986 rules.
The Critical Difference
| Scenario | URLEncoder | URI class |
|---|---|---|
| Space encoding | + | %20 |
& encoding | %26 | %26 (in query) |
| Period encoding | not encoded | not encoded |
| Asterisk encoding | * preserved | * encoded as %2A |
| Tilde encoding | ~ preserved | ~ encoded as %7E (in some versions) |
| Best for | Form data | URL components |
Building a Query String in Java
Approach 1: Manual Concatenation with URLEncoder
public static String buildUrl(String base, Map<String, String> params) {
StringBuilder query = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (query.length() > 0) query.append("&");
query.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
query.append("=");
query.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
}
return base + "?" + query.toString();
}
Usage:
Map<String, String> params = new HashMap<>();
params.put("q", "hello world");
params.put("category", "books & media");
String url = buildUrl("https://example.com/search", params);
System.out.println(url);
Output:
https://example.com/search?q=hello+world&category=books+%26+media
Note the + for spaces. Some API providers will reject this or decode it incorrectly.
Approach 2: Using the URI Class
public static String buildUri(String base, Map<String, String> params)
throws URISyntaxException {
StringBuilder query = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (query.length() > 0) query.append("&");
query.append(entry.getKey());
query.append("=");
query.append(entry.getValue());
}
URI uri = new URI(base + "?" + query.toString());
return uri.toString();
}
Wait — this does not work as expected because the raw input is passed to URI which may not encode it.
Approach 3: Using the Multi-Argument URI Constructor
public static String buildUriCorrect(
String scheme, String host, String path,
Map<String, String> params) throws URISyntaxException {
StringBuilder query = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
if (query.length() > 0) query.append("&");
query.append(entry.getKey());
query.append("=");
query.append(entry.getValue());
}
URI uri = new URI(scheme, host, path, query.toString(), null);
return uri.toString();
}
This is the safest approach. The URI constructor encodes the query component according to RFC 3986.
Handling OAuth Redirect URIs
OAuth redirect URIs are a common pain point in Java URL encoding.
The Problem
String callback = "https://myapp.com/callback?state=abc123";
String encoded = URLEncoder.encode(callback, "UTF-8");
String oauthUrl = "https://auth.com/oauth/authorize?redirect_uri=" + encoded;
URLEncoder produces + for spaces (if any exist), which some OAuth providers reject.
The Fix
String callback = "https://myapp.com/callback?state=abc123";
URI uri = new URI(
"https",
"auth.com",
"/oauth/authorize",
"redirect_uri=" + callback,
null
);
System.out.println(uri.toString());
Output:
https://auth.com/oauth/authorize?redirect_uri=https%3A%2F%2Fmyapp.com%2Fcallback%3Fstate%3Dabc123
This produces RFC 3986-compliant encoding that most providers accept.
When to Use Which
Use URLEncoder when:
- Encoding form data (
application/x-www-form-urlencoded) - Building POST body parameters
- Working with legacy APIs that expect
+for spaces - Encoding individual values that will be manually concatenated
Use URI class when:
- Building complete URLs
- Encoding query parameters for REST APIs
- Constructing OAuth redirect URIs
- Working with RFC 3986 compliant services
- Encoding path segments
Modern Alternative: Spring's UriComponentsBuilder
If you use Spring, UriComponentsBuilder provides a clean API:
import org.springframework.web.util.UriComponentsBuilder;
String url = UriComponentsBuilder
.fromHttpUrl("https://example.com/search")
.queryParam("q", "hello world & java")
.queryParam("category", "tutorials")
.build()
.toUriString();
System.out.println(url);
Output:
https://example.com/search?q=hello%20world%20%26%20java&category=tutorials
This produces proper RFC 3986 encoding with %20 for spaces.
Encoding Path Segments in Java
Path segments need different encoding rules from query parameters.
public static String encodePathSegment(String segment) {
try {
// Encode and then restore / separators
String encoded = URLEncoder.encode(segment, "UTF-8")
.replace("+", "%20")
.replace("%2F", "/");
return encoded;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
Better approach with URI:
URI uri = new URI("https", "example.com", "/products/" + productName, null, null);
The URI constructor correctly encodes path components.
Decoding in Java
Decoding with URLDecoder
import java.net.URLDecoder;
String decoded = URLDecoder.decode("hello+world", "UTF-8");
System.out.println(decoded); // hello world
URLDecoder converts both + and %20 to spaces.
String decoded = URLDecoder.decode("hello%20world", "UTF-8");
System.out.println(decoded); // hello world
Decoding with URI
URI uri = new URI("https://example.com/search?q=hello%20world");
String query = uri.getQuery(); // q=hello%20world
String rawQuery = uri.getRawQuery(); // q=hello%20world
URI.getQuery() returns the decoded query string, while getRawQuery() returns the percent-encoded version.
Testing URL Encoding in Java
import org.junit.jupiter.api.Test;
import java.net.URLEncoder;
import java.net.URI;
import static org.junit.jupiter.api.Assertions.*;
class UrlEncodingTest {
@Test
void testUrlEncoder() throws Exception {
assertEquals("hello+world", URLEncoder.encode("hello world", "UTF-8"));
assertEquals("C%2B%2B", URLEncoder.encode("C++", "UTF-8"));
assertEquals("50%25+off", URLEncoder.encode("50% off", "UTF-8"));
}
@Test
void testUriEncoding() throws Exception {
URI uri = new URI("https", "example.com", "/search",
"q=hello world&category=tutorials", null);
assertTrue(uri.toString().contains("hello%20world"));
assertTrue(uri.toString().contains("category=tutorials"));
}
@Test
void testRoundTrip() throws Exception {
String original = "hello world & java 东京";
String encoded = URLEncoder.encode(original, "UTF-8")
.replace("+", "%20");
String decoded = URLDecoder.decode(encoded, "UTF-8");
assertEquals(original, decoded);
}
}
Best Practices for Java URL Encoding
Prefer URI Multi-Argument Constructors
// Good
new URI("https", "api.example.com", "/search", "q=" + value, null);
// Avoid
new URI("https://api.example.com/search?q=" + URLEncoder.encode(value, "UTF-8"));
Replace + with %20 When Needed
String encoded = URLEncoder.encode(value, "UTF-8")
.replace("+", "%20");
Decode Incoming Parameters Explicitly
String param = request.getParameter("q");
if (param != null) {
param = URLDecoder.decode(param, "UTF-8");
}
Use Modern Libraries
// Spring Boot
UriComponentsBuilder.fromUriString(base)
.queryParam("key", value)
.build()
.toUri();
Test with Edge Cases
Always test encoding with:
hello world (space)
C++ (plus sign)
50% off (percent sign)
a=b&c (ampersand and equals)
東京 (unicode)
Related Resources
For more on encoding across different languages and handling common pitfalls:
-
Common URL Encoding Mistakes Developers Keep Making Common Mistakes
-
How to Encode a URL in JavaScript JavaScript URL Encoding
-
URL Encoding in C# C# URL Encoding Guide
-
Double URL Encoding — How It Happens Double URL Encoding
FAQ
What is the difference between URLEncoder and URI for URL encoding?
URLEncoder follows form-urlencoded rules (spaces become +). The URI class follows RFC 3986 (spaces become %20).
Why does URLEncoder.encode() produce + for spaces?
URLEncoder was designed for application/x-www-form-urlencoded encoding, which uses + for spaces.
Should I use URLEncoder or URI for REST API query parameters?
Prefer the URI class for REST APIs. It produces standard percent-encoding with %20 for spaces.
How do I encode a plus sign in Java?
URLEncoder.encode() produces %2B for literal plus signs. The URI class also encodes + as %2B in query components.
How do I decode URL-encoded parameters in Java?
Use URLDecoder.decode(value, "UTF-8"). It handles both + and %20 as spaces.
Does Spring provide a better URL encoding API?
Yes. UriComponentsBuilder provides a clean, standards-compliant API for constructing encoded URLs.
How do I handle Japanese or Chinese characters in URL encoding?
Both URLEncoder (with UTF-8) and the URI class handle unicode characters correctly. Always specify "UTF-8" with URLEncoder.
Final Thoughts
Java's two URL encoding approaches exist for historical reasons, but the distinction is important. URLEncoder is a legacy utility designed for form encoding. The URI class is the modern, standards-compliant way to construct URLs.
If you are building new integrations, prefer the URI multi-argument constructor. It produces RFC 3986 compliant output, handles each URL component correctly, and avoids the + vs %20 confusion.
For debugging encoded URLs during development, the URL Encoder/Decoder tool helps verify how different encoding options affect your parameters.