Why Are There So Many URI Methods?
23 Apr 2025If you have worked with Spring Framework’s HTTP clients—RestTemplate
, WebClient
, or RestClient
—you may have noticed that each of them offers multiple overloaded methods to specify the request URI.
Three Ways to specify a URL
Take RestClient
as an example (ignoring the builder-based variants).
It offers the following overloads:
uri(String uriTemplate, Object... uriVariables)
uri(String uriTemplate, Map<String, ?> uriVariables)
uri(URI uri)
WebClient
has the exact same design, and RestTemplate
has comparable variants for each HTTP method.
The Varargs Variant
The first form, using a String
and Object...
, is the most commonly used.
It allows straightforward substitution of URI variables using a varargs array:
restClient.get().uri("https://example.com/{path}", "foo");
The Map
Variant
The second form, which takes a String
and a Map<String, ?>
, allows you to substitute the same variable more than once:
Map<String, ?> vars = Map.of("path", "foo");
restClient.get().uri("https://example.com/{path}/{path}", vars);
The URI
Variant
The third variant takes a pre-constructed java.net.URI
:
restClient.get().uri(new URI("https://example.com/foo"));
At first glance, this might seem redundant. Why not just pass the URI as a string?
restClient.get().uri("https://example.com/foo");
In this simple case, both calls behave the same. But under the hood, there is a subtle and important difference related to URI encoding.
Why Parameter Types Matter
When you pass a String
, Spring will encode the URI for you.
But if your string is already encoded, this can lead to double-encoding.
For example:
restClient.get().uri("http://example.com/foo%20bar");
This looks like a request to http://example.com/foo bar
, but it will actually go to http://example.com/foo%2520bar
.
The %
character is not a valid character in a URI path—it gets encoded again as %25
.
Why Not Be Smarter?
You might wonder why Spring does not automatically detect whether a string is already encoded. The challenge is that what seems “smart” to one developer may break expectations for another.
Some applications expect percent signs to be encoded again. Trying to infer the correct behavior would introduce ambiguity and inconsistencies.
Avoiding Ambiguity
This is exactly why the third method exists.
When you pass a URI
, Spring can safely assume that you have already handled encoding, because the Javadoc of URI(String)
specifies:
The single-argument constructor requires any illegal characters in its argument to be quoted and preserves any escaped octets and other characters that are present.
By using URI
explicitly, you avoid any ambiguity.
You are telling Spring: do not touch this—I know what I am doing.
Avoid strings where other types are more appropriate
Joshua Bloch summarizes this concept in Effective Java:
Avoid the natural tendency to represent objects as strings when better data types exist or can be written. Used inappropriately, strings are more cumbersome, less flexible, and more error-prone that other types.
– Effective Java, Third Edition (Item 62)
Passing a URI
instead of a String
is a textbook example of this principle.
The Broader Lesson
Using specific types in method signatures helps enforce correctness. For example:
- A method that accepts a
URI
guarantees that encoding has already been handled. - A method that accepts a
java.time
object likeLocalDate
orZonedDateTime
guarantees that parsing and validation are complete.
In contrast, accepting a String
means carrying the uncertainty of format, validity, and edge cases.
When designing APIs, prefer specific, well-defined types over general-purpose strings. Doing so makes your contracts clearer, reduces bugs, and makes your code easier to reason about—for both users and maintainers.