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 class
  • java.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;
ApproachMethodStandard
URLEncoderURLEncoder.encode(input, "UTF-8")application/x-www-form-urlencoded
URI classnew 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

CharacterEncoded
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 %26 in 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

ScenarioURLEncoderURI class
Space encoding+%20
& encoding%26%26 (in query)
Period encodingnot encodednot encoded
Asterisk encoding* preserved* encoded as %2A
Tilde encoding~ preserved~ encoded as %7E (in some versions)
Best forForm dataURL 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:


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.