Poutsma Principles Consulting & Training

Why Are There So Many URI Methods?

If 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 like LocalDate or ZonedDateTime 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.

About the Author

I am Arjen Poutsma, and I help teams improve Java code, design better APIs, and manage open source—through reviews, training, and coaching. If that sounds useful, please contact me.