Skip to content

Join collection matrix variable values with a comma - #1421

Open
kdelay wants to merge 4 commits into
spring-cloud:mainfrom
kdelay:fix/matrix-variable-collection-values-50x
Open

kdelay wants to merge 4 commits into
spring-cloud:mainfrom
kdelay:fix/matrix-variable-collection-values-50x

Conversation

@kdelay

@kdelay kdelay commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

MatrixVariableParameterProcessor builds the path segment from each value's toString(), so a collection value comes out bracketed.

For @MatrixVariable Map<String, List<String>> matrixVars (the signature used as the example in the reference docs) with colours=[red, blue] and size=L, it produces ;colours=[red, blue];size=L. A matrix variable separates repeated values with a comma, so WebUtils.parseMatrixVariables (Spring 7.1.0-SNAPSHOT) reads that back as {colours=[[red, blue]], size=[L]}. With ;colours=red,blue;size=L it reads {colours=[red, blue], size=[L]}.

The single-object branch had the same problem, so @MatrixVariable("colours") List<String> is covered too.

./mvnw -P spring -pl spring-cloud-openfeign-core verify: 445 tests, 0 failures. Both added tests fail on main without the change.

@ryanjbaxter

Copy link
Copy Markdown
Contributor

Since this changes the return format, I do not want to put this in 5.0.x but it is OK to put in main

@kdelay
kdelay force-pushed the fix/matrix-variable-collection-values-50x branch from f1b2108 to 0e58e3a Compare September 18, 2026 13:20
@kdelay
kdelay changed the base branch from 5.0.x to main September 18, 2026 13:20
@kdelay

kdelay commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

Retargeted to main and rebased onto d4d8148 (0e58e3a). Re-ran on that base: reverting only MatrixVariableParameterProcessor leaves the two added tests failing (SpringMvcContractTests:832 and :840) with the rest of the 71 passing; with the change, ./mvnw -P spring -pl spring-cloud-openfeign-core verify gives 445 tests, 0 failures, 0 Checkstyle violations.

@ryanjbaxter

Copy link
Copy Markdown
Contributor

Please sign your commit so the DCO passes

MatrixVariableParameterProcessor built the path segment from the raw
toString() of each value, so a collection came out bracketed:
;colours=[red, blue] instead of ;colours=red,blue. A matrix variable
separates repeated values with a comma, so the receiving side reads
"[red" and " blue]" back out of the bracketed form.

Both branches of the processor were affected, including the
Map<String, List<String>> signature used as the @MatrixVariable example
in the reference documentation.

Signed-off-by: kdelay <kdelay20@gmail.com>
@kdelay
kdelay force-pushed the fix/matrix-variable-collection-values-50x branch from 0e58e3a to bd2b41f Compare September 18, 2026 13:54
@kdelay

kdelay commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

Re-pushed the commit (bd2b41f, same tree as 0e58e3a) with the sign-off. DCO is green now.

assertThat(";param=value").isEqualTo(data.indexToExpander().get(0).expand(testMap));
}

@Test

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a test through a real Feign client and/or RestTemplate? I believe RequestTemplateFactoryResolver.expandElements() will add a , in the result as well


private String expandValue(Object value) {
if (value instanceof Collection<?> values) {
return values.stream().filter(Objects::nonNull).map(Object::toString).collect(Collectors.joining(","));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use org.springframework.util.StringUtils.collectionToCommaDelimitedString

Use StringUtils.collectionToCommaDelimitedString for the join and flatten arrays and nested collections through the same path.

Signed-off-by: kdelay <kdelay20@gmail.com>
@kdelay

kdelay commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

Done in 4bb1784: join via StringUtils.collectionToCommaDelimitedString, arrays through the same path (CollectionUtils.arrayToList), so nested collections/arrays flatten too. Reverting only the processor leaves the 4 added tests failing and the other 69 passing; verify is 447 tests, 0 failures, 0 Checkstyle violations.

You are right about expandElements. With a stub Client a List param gives /matrixVariable/%3Bcolours%3Dred,%3Bcolours%3Dblue, a Map param %3Bcolours%3Dred%2Cblue%3Bsize%3DL, on main too. Should I open a separate issue for that?

@ryanjbaxter

Copy link
Copy Markdown
Contributor

You are right about expandElements. With a stub Client a List param gives /matrixVariable/%3Bcolours%3Dred,%3Bcolours%3Dblue, a Map param %3Bcolours%3Dred%2Cblue%3Bsize%3DL, on main too. Should I open a separate issue for that?

No, IMO we should deal with it as part of this change

Feign pct-encodes every value it substitutes into a URI template, so the
';' and '=' produced by the expander left the segment as %3Bname%3Dvalue and
the server could not read it as matrix variables. Move the ';name=' prefix
into the URI template, where it survives as a literal, and let the expander
produce only the value. A collection parameter is expanded element by element
by Feign and joined with ',', which now yields ';colours=red,blue'.

Signed-off-by: kdelay <kdelay20@gmail.com>
@kdelay

kdelay commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

Done in 02fbad7. Feign pct-encodes every value substituted into a URI template, so the separators came out as %3B/%3D. The ;name= prefix now lives in the template, where it stays literal.

Measured through a stub Client (2 tests added): String was %3Bparam%3Dvalue, now ;param=value; List was %3Bcolours%3Dred,%3Bcolours%3Dblue, now ;colours=red,blue.

Arrays (not Iterable, so joined inside the value) and Map (dynamic keys) still encode; both need reserved characters inside a value, which UriTemplate forbids.

verify: 449 tests, 0 failures.

}

private String expandValue(Object value) {
if (value.getClass().isArray()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the Map-typed @MatrixVariable case case is handled properly

Let Feign expand a Map typed matrix variable through a path-style URI template expression, so the ; and = separators stay literal in the request URL and only the keys and the values are encoded.

Signed-off-by: kdelay <kdelay20@gmail.com>
@kdelay

kdelay commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

Done in 94a9ad3: the Map variable is now a path-style expression ({;params}) expanded by Feign, so ; and = stay literal. {"colours":"red","size":"L"} was %3Bcolours%3Dred%3Bsize%3DL, now ;colours=red;size=L. verify: 449 tests, 0 failures.

Collection-valued entries are still not covered: expandMap encodes each value without recursing, so ["red","blue"] gives ;colours=%5Bred%2C%20blue%5D, and joining them myself yields red%2Cblue, a single value. The reserved-safe form is a repeated name, which Feign emits for a top-level Iterable only. I can raise that with Feign.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants