URLEncoder in Java is a class for percent-encoding strings into application/x-www-form-urlencoded format, and the single most common bug is using it to encode a whole URL when it is actually designed to encode individual query-parameter values. Misusing it produces broken links at best and injection vulnerabilities at worst. This guide covers what URLEncoder.encode is really for, the space-versus-plus trap, and where encoding mistakes turn into security problems.
What URLEncoder actually encodes
The class encodes a string into the same format an HTML form submits with Content-Type: application/x-www-form-urlencoded. Always pass the charset explicitly; the no-charset overload is deprecated and platform-dependent:
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
String value = "a b&c=d";
String encoded = URLEncoder.encode(value, StandardCharsets.UTF_8);
// a+b%26c%3Dd
Notice two things. The ampersand and equals sign were encoded, which is correct for a value that should not be interpreted as a parameter separator. And the space became +, not %20. That plus-for-space behavior is specific to the x-www-form-urlencoded format, and it is the root of most confusion.
Why you must not encode a whole URL with it
If you hand a full URL to URLEncoder.encode, it will encode the ://, the slashes, and the ?, producing a string that is no longer a URL:
// WRONG
URLEncoder.encode("https://example.com/path?q=1", StandardCharsets.UTF_8);
// https%3A%2F%2Fexample.com%2Fpath%3Fq%3D1
URLEncoder is a component encoder, not a URL encoder. You encode each dynamic value and assemble the URL around it, or you use a builder that understands URL structure. Since Java 11+, the modern approach uses URI construction or a framework builder rather than string concatenation:
String q = URLEncoder.encode(userInput, StandardCharsets.UTF_8);
String url = "https://example.com/search?q=" + q;
For path segments the rules differ again, because + is not a space in a path, only in a query string. If you URLEncoder.encode a value and drop it into a path segment, a literal space becomes + and is interpreted as a plus character by the server, not a space. For path components use URI or explicitly convert + handling; do not reuse the form encoder blindly.
Where this becomes a security issue
Encoding is not decoration; it is a boundary control. Getting it wrong breaks the separation between data and structure, which is the definition of an injection class.
Open redirects and SSRF. If you build an outbound URL by concatenating unencoded user input, an attacker can inject &, #, or a full ://host and redirect the request or point a server-side fetch at an internal address. Encoding the value with URLEncoder neutralizes the separators so the input stays a value. The defensive rule is to encode and validate the target against an allowlist, not rely on encoding alone.
HTTP response splitting / header injection. When user input flows into a Location header or a constructed URL that becomes a redirect, unencoded CR/LF or separator characters can split the response or inject parameters. Proper value encoding removes the structural characters an attacker needs.
Double-encoding confusion. A subtle failure mode is encoding twice, or encoding then having a framework encode again. A %26 becomes %2526, and downstream decoding can reintroduce a separator you thought you had neutralized. Encode exactly once, at the point of assembly, and never re-encode an already-encoded string.
The decoding side matters too
URLDecoder.decode is the inverse, and it also turns + back into a space. If you decode a path component with it, a legitimate + in the data is silently converted to a space, corrupting the value. Match your decoder to how the value was encoded and where it lives in the URL. Mismatched encode/decode pairs are a steady source of both functional bugs and validation bypasses, because a filter that inspects the pre-decoded value never sees the character the application ultimately acts on.
Practical guidance
- Always pass a charset, and use
StandardCharsets.UTF_8. Never rely on the deprecated single-argument overload. - Encode individual values, never whole URLs. Build the URL structurally around encoded components.
- Remember
+means space only in query strings, not in paths. UseURIfor path segments. - Encode once. Track whether a string is already encoded to avoid double-encoding.
- Encoding is not validation. For destinations, credentials, or anything security-sensitive, validate against an allowlist in addition to encoding.
These are the kinds of subtle, easy-to-miss defects that show up in code review and in static analysis rather than in dependency scans, since they live in first-party code. For the dependency side of Java security, the Jackson databind security guide covers a related class of risk in a widely used library, and the input-validation fundamentals material puts encoding in the broader context of untrusted-data handling.
FAQ
What is the difference between URLEncoder.encode and encoding a full URL?
URLEncoder.encode produces application/x-www-form-urlencoded output meant for individual query-parameter values. It will encode the ://, slashes, and ? in a full URL, breaking it. Encode each value separately and assemble the URL around the results.
Why does Java's URLEncoder turn spaces into + instead of %20?
Because URLEncoder implements the form-encoding format, where a space is represented as +. This is correct in a query string but wrong in a URL path, where + is a literal plus. Use URI construction for path segments.
Can misusing URLEncoder cause a security vulnerability?
Yes. Failing to encode user input in outbound URLs enables open redirects, server-side request forgery, and header injection, because unencoded separators let attackers alter the URL structure. Encode values and also validate destinations against an allowlist.
Should I ever use the single-argument URLEncoder.encode(String)?
No. That overload is deprecated and uses a platform-default charset, which makes output non-portable. Always call URLEncoder.encode(value, StandardCharsets.UTF_8) with an explicit charset.