With spring-web 6.2.5, I get the following behavior :
System.out.println(UriComponentsBuilder.fromUriString(“http://myhost?a={p1}&b={p2}&a={p3}”).buildAndExpand(“a1”, “b1”, “a2”));
Output :
http://myhost?a=a1&a=b1&b=a2
I was expecting the following output :
http://myhost?a=a1&b=b1&a=a2
It seems that query params are reordered.
Even if it’s not a good idea to rely on query parameter order, it seems to be legal (see stackoverflow > Is it legal or safe to depend on the ordering of URL query parameters?)… So I think that things like RestClient may require proper query parameter order handling.
Furthermore, query params reordering seems to happen before uri template variables are expanded so values get swapped when given from an array (ie not from map) as the order of variables is then significant.
Comment From: christophejan
If my understanding is correct, URI RFC doesn’t specify query parameters. In current WHATWG HTLM standard, 4.10.21.7 URL-encoded form data just point to WHATWG URL standard 5 application/x-www-form-urlencoded that state :
The application/x-www-form-urlencoded format provides a way to encode a list of tuples, each consisting of a name and a value.
This section also details in depth the related parsing and serializing.
I think that something based on a List of name value tuple could be more appropriate as internal model of query parameter in UriComponents than the current MultiValueMap
I think a such change may be also the opportunity to potentially design a class for such list of name value tuple with all the helping methods (get the value for the first tuple of name, from / to MultiValueMap conversion etc.) to be possibly used elsewhere. It could be class dedicated to query parameters like HttpHeaders does or a more generic one.
It is, of course, possible I just misunderstood something.
Comment From: rstoyanchev
We generally preserve the order of query parameters and their values. The only exception is when multiple values for the same query parameter are not specified together (and have other query parameters in between). This is due to the use of a MultiValueMap for internal storage that groups by query parameter name.
We do support the WhatWG URL living standard as of the parsing algorithms, but that's just the parsing side.
Arguably, we could do better with this, but there are options for you to eliminate ambiguity if this is a URI template that's in your control. Either declare query parameter values together, or use the buildAndExpand variant with a Map, or initialize the query parameters in a MultiValueMap.
Comment From: christophejan
There is indeed workarounds but the current behavior is : * error prone (require some knowledge of the implementation storage) * limiting (some api may require to not follow the current params grouping)
Leaving the MultiValueMap storage for a list of tuple would solve that and be closer to the specification.
I think that it could be done without breaking the backward compatibility and without introducing any drawbacks.
UriComponent is widely used so please consider it may worth this effort.
Comment From: rstoyanchev
Indeed there is room for improvement so I will mark the issue accordingly.
Comment From: Mamun-Al-Babu-Shikder
The current behavior of UriComponentsBuilder using MultiValueMap
To overcome this, an internal switch from MultiValueMap to a List
For example, instead of storing: { a: [x, z], b: [y] } We store: [(a, x), (a, z), (b, y)]
Then serialize the query string based on insertion order.
This would make URI expansion more intuitive and compliant with real-world API expectations, and could be avoid breaking existing consumers.
Comment From: SuganthiThomas
Hi, I’m interested in working on this issue. I have experience with Java and Spring Boot. Could I take this up?
Comment From: rstoyanchev
@SuganthiThomas feel free to give it a try. A couple of notes.
ListofMap.Entrywould be fine, and this can remain an implementation detail. In other words, no need to expose that via public methods.replaceQueryParammethods should replace all values for a key regardless of how many or in what order as per their Javadoc.
Comment From: SuganthiThomas
@rstoyanchev , i have tried
I did a quick test with UriComponentsBuilder and noticed different behavior depending on how query params are constructed.
Case 1: Using buildAndExpand
String uri = UriComponentsBuilder .fromUriString("http://myhost?a={p1}&b={p2}&a={p3}") .buildAndExpand(Map.of("p1", "a1", "p2", "b1", "p3", "a2")) .toUriString(); http://myhost?a=a1&a=b1&b=a2
Here the placeholders are expanded, but the order/duplication of query params is not preserved.
Case 2: Using queryParam(...)
String uri = UriComponentsBuilder .fromUriString("http://myhost") .queryParam("a", "a1") .queryParam("b", "b1") .queryParam("a", "a2") .toUriString();
http://myhost?a=a1&b=b1&a=a2
In this case the order and duplicates are preserved correctly.
Comment From: rstoyanchev
I can't reproduce what you're describing, and I don't see how it could happen given the underlying MultiValueMap storage.
In both cases I get http://myhost?a=a1&a=a2&b=b1.
Comment From: novykovd
guys i have a working solution is it fine if i open a pr? but to be fair my solution is with a separate pair util class