diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java index 5cfd6011..7fa082a3 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringEncoder.java @@ -24,6 +24,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; +import java.util.Objects; import java.util.stream.Stream; import feign.RequestTemplate; @@ -48,6 +49,10 @@ import org.springframework.web.multipart.MultipartFile; import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders; import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; +import static org.springframework.http.MediaType.MULTIPART_MIXED; +import static org.springframework.http.MediaType.MULTIPART_RELATED; /** * @author Spencer Gibb @@ -56,6 +61,7 @@ import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHead * @author Aaron Whiteside * @author Darren Foong * @author Olga Maciaszek-Sharma + * @author Can Bezmen */ @SuppressWarnings("rawtypes") public class SpringEncoder implements Encoder { @@ -95,8 +101,8 @@ public class SpringEncoder implements Encoder { requestContentType = MediaType.valueOf(type); } - if (isMultipartType(requestContentType)) { - this.springFormEncoder.encode(requestBody, bodyType, request); + if (isFormRelatedContentType(requestContentType)) { + springFormEncoder.encode(requestBody, bodyType, request); return; } else { @@ -207,9 +213,16 @@ public class SpringEncoder implements Encoder { } } + private boolean isFormRelatedContentType(MediaType requestContentType) { + return isMultipartType(requestContentType) || isFormUrlEncoded(requestContentType); + } + private boolean isMultipartType(MediaType requestContentType) { - return Arrays.asList(MediaType.MULTIPART_FORM_DATA, MediaType.MULTIPART_MIXED, MediaType.MULTIPART_RELATED) - .contains(requestContentType); + return Arrays.asList(MULTIPART_FORM_DATA, MULTIPART_MIXED, MULTIPART_RELATED).contains(requestContentType); + } + + private boolean isFormUrlEncoded(MediaType requestContentType) { + return Objects.equals(APPLICATION_FORM_URLENCODED, requestContentType); } private boolean binaryContentType(FeignOutputMessage outputMessage) { @@ -227,21 +240,21 @@ public class SpringEncoder implements Encoder { private final HttpHeaders httpHeaders; private FeignOutputMessage(RequestTemplate request) { - this.httpHeaders = getHttpHeaders(request.headers()); + httpHeaders = getHttpHeaders(request.headers()); } @Override public OutputStream getBody() throws IOException { - return this.outputStream; + return outputStream; } @Override public HttpHeaders getHeaders() { - return this.httpHeaders; + return httpHeaders; } public ByteArrayOutputStream getOutputStream() { - return this.outputStream; + return outputStream; } } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java index 5b30e213..600d2e88 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringEncoderTests.java @@ -60,6 +60,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.http.HttpHeaders.ACCEPT; import static org.springframework.http.HttpHeaders.CONTENT_LENGTH; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; @@ -68,6 +69,7 @@ import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; * @author Spencer Gibb * @author Olga Maciaszek-Sharma * @author Ahmad Mozafarnia + * @author Can Bezmen */ @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = SpringEncoderTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, @@ -179,6 +181,17 @@ public class SpringEncoderTests { assertThat(new String(request.requestBody().asBytes())).as("Body content cannot be decoded").contains("hi"); } + @Test + public void testFromURLEncodedValue() { + Encoder encoder = context.getInstance("formUrlEncoded", Encoder.class); + assertThat(encoder).isNotNull(); + RequestTemplate request = new RequestTemplate(); + request.header(CONTENT_TYPE, APPLICATION_FORM_URLENCODED_VALUE); + String body = "test"; + encoder.encode(body, String.class, request); + assertThat(new String(request.requestBody().asBytes())).as("Body content cannot be decoded").contains(body); + } + @Test public void testNoCharsetForBinaryFiles() { Encoder encoder = context.getInstance("test", Encoder.class); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java index 83b97771..be7ba519 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java @@ -38,7 +38,6 @@ import javax.servlet.http.Part; import feign.Client; import feign.Logger; import feign.RequestInterceptor; -import feign.RequestTemplate; import feign.codec.EncodeException; import org.junit.jupiter.api.Test; @@ -65,7 +64,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.format.Formatter; -import org.springframework.format.FormatterRegistry; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpEntity; import org.springframework.http.HttpStatus; @@ -73,6 +71,8 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; @@ -84,6 +84,9 @@ import org.springframework.web.multipart.MultipartFile; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; /** * @author Spencer Gibb @@ -277,6 +280,13 @@ class ValidFeignClientTests { assertThat(testClient.getToString(args)).isEqualTo(expectedResult); } + @Test + void testFormURLEncoded() { + Hello hello = new Hello(HELLO_WORLD_1); + Hello response = testClient.postFormUrlEncoded(hello); + assertThat(response).isEqualTo(hello); + } + @Test void namedFeignClientWorks() { assertThat(namedFeignClient).as("namedFeignClient was null").isNotNull(); @@ -382,52 +392,42 @@ class ValidFeignClientTests { @FeignClient(name = "localapp8") protected interface MultipartClient { - @RequestMapping(method = RequestMethod.POST, path = "/singlePart", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/singlePart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String singlePart(@RequestPart("hello") String hello); - @RequestMapping(method = RequestMethod.POST, path = "/singlePojoPart", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/singlePojoPart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String singlePojoPart(@RequestPart("hello") Hello hello); - @RequestMapping(method = RequestMethod.POST, path = "/multipart", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world, @RequestPart("file") MultipartFile file); - @RequestMapping(method = RequestMethod.POST, path = "/multipartPojo", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartPojo", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world, @RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2, @RequestPart("file") MultipartFile file); - @RequestMapping(method = RequestMethod.POST, path = "/multipartNames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestPartListOfMultipartFilesReturnsPartNames(@RequestPart("files") List files); - @RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartFilenames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestPartListOfMultipartFilesReturnsFileNames(@RequestPart("files") List files); - @RequestMapping(method = RequestMethod.POST, path = "/multipartPojosFiles", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartPojosFiles", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List pojos, @RequestPart("files") List files); - @RequestMapping(method = RequestMethod.POST, path = "/multipartNames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestBodyListOfMultipartFiles(@RequestBody List files); - @RequestMapping(method = RequestMethod.POST, path = "/multipartNames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestBodySingleMultipartFile(@RequestBody MultipartFile file); - @RequestMapping(method = RequestMethod.POST, path = "/multipartNames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestBodyMap(@RequestBody Map form); - @RequestMapping(method = RequestMethod.POST, path = "/invalid", - consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/invalid", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, + produces = TEXT_PLAIN_VALUE) String invalid(@RequestBody MultipartFile file); } @@ -435,36 +435,36 @@ class ValidFeignClientTests { @FeignClient(name = "localapp", configuration = TestClientConfig.class) protected interface TestClient { - @RequestMapping(method = RequestMethod.GET, path = "/hello") + @GetMapping("/hello") Hello getHello(); - @RequestMapping(method = RequestMethod.GET, path = "/hello") + @GetMapping("/hello") Optional getOptionalHello(); - @RequestMapping(method = RequestMethod.GET, path = "${feignClient.methodLevelRequestMappingPath}") + @GetMapping("${feignClient.methodLevelRequestMappingPath}") Hello getHelloUsingPropertyPlaceHolder(); - @RequestMapping(method = RequestMethod.GET, path = "/hellos") + @GetMapping("/hellos") List getHellos(); - @RequestMapping(method = RequestMethod.GET, path = "/hellostrings") + @GetMapping("/hellostrings") List getHelloStrings(); - @RequestMapping(method = RequestMethod.GET, path = "/helloheaders") + @GetMapping("/helloheaders") List getHelloHeaders(); - @RequestMapping(method = RequestMethod.GET, path = "/helloheadersplaceholders", + @GetMapping(path = "/helloheadersplaceholders", headers = "myPlaceholderHeader=${feignClient.myPlaceholderHeader}") String getHelloHeadersPlaceholders(); - @RequestMapping(method = RequestMethod.GET, path = "/helloparams") + @GetMapping("/helloparams") List getParams(@RequestParam("params") List params); - @RequestMapping(method = RequestMethod.GET, path = "/formattedparams") + @GetMapping("/formattedparams") List getFormattedParams( @RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List params); - @RequestMapping(method = RequestMethod.GET, path = "/noContent") + @GetMapping("/noContent") ResponseEntity noContent(); @RequestMapping(method = RequestMethod.HEAD, path = "/head") @@ -477,21 +477,24 @@ class ValidFeignClientTests { produces = "application/vnd.io.spring.cloud.test.v1+json", path = "/complex") String moreComplexContentType(String body); - @RequestMapping(method = RequestMethod.GET, path = "/tostring") + @GetMapping("/tostring") String getToString(@RequestParam("arg") Arg arg); - @RequestMapping(method = RequestMethod.GET, path = "/tostring2") + @GetMapping("/tostring2") String getToString(@RequestParam("arg") OtherArg arg); - @RequestMapping(method = RequestMethod.GET, path = "/tostringcollection") + @GetMapping("/tostringcollection") Collection getToString(@RequestParam("arg") Collection args); + @PostMapping(path = "/form-urlencoded", consumes = APPLICATION_FORM_URLENCODED_VALUE) + Hello postFormUrlEncoded(Hello hello); + } @FeignClient(name = "localapp1") protected interface TestClientServiceId { - @RequestMapping(method = RequestMethod.GET, path = "/hello") + @GetMapping("/hello") Hello getHello(); } @@ -499,10 +502,10 @@ class ValidFeignClientTests { @FeignClient(name = "localapp2", decode404 = true) protected interface DecodingTestClient { - @RequestMapping(method = RequestMethod.GET, path = "/notFound") + @GetMapping("/notFound") ResponseEntity notFound(); - @RequestMapping(method = RequestMethod.GET, path = "/notFound") + @GetMapping("/notFound") Optional optional(); } @@ -526,22 +529,12 @@ class ValidFeignClientTests { @Bean public RequestInterceptor interceptor1() { - return new RequestInterceptor() { - @Override - public void apply(RequestTemplate template) { - template.header(MYHEADER1, "myheader1value"); - } - }; + return template -> template.header(MYHEADER1, "myheader1value"); } @Bean public RequestInterceptor interceptor2() { - return new RequestInterceptor() { - @Override - public void apply(RequestTemplate template) { - template.header(MYHEADER2, "myheader2value"); - } - }; + return template -> template.header(MYHEADER2, "myheader2value"); } } @@ -570,27 +563,21 @@ class ValidFeignClientTests { @Bean FeignFormatterRegistrar feignFormatterRegistrar() { - return new FeignFormatterRegistrar() { + return registry -> registry.addFormatter(new Formatter() { @Override - public void registerFormatters(FormatterRegistry registry) { - registry.addFormatter(new Formatter() { - - @Override - public String print(OtherArg object, Locale locale) { - if ("foo".equals(object.value)) { - return "bar"; - } - return object.value; - } - - @Override - public OtherArg parse(String text, Locale locale) throws ParseException { - return new OtherArg(text); - } - }); + public String print(OtherArg object, Locale locale) { + if ("foo".equals(object.value)) { + return "bar"; + } + return object.value; } - }; + + @Override + public OtherArg parse(String text, Locale locale) throws ParseException { + return new OtherArg(text); + } + }); } @Bean @@ -598,23 +585,23 @@ class ValidFeignClientTests { return new JsonFormWriter(); } - @RequestMapping(method = RequestMethod.GET, path = "/hello") + @GetMapping("/hello") public Hello getHello() { return new Hello(HELLO_WORLD_1); } - @RequestMapping(method = RequestMethod.GET, path = "/hello2") + @GetMapping("/hello2") public Hello getHello2() { return new Hello(OI_TERRA_2); } - @RequestMapping(method = RequestMethod.GET, path = "/hellos") + @GetMapping("/hellos") public List getHellos() { ArrayList hellos = getHelloList(); return hellos; } - @RequestMapping(method = RequestMethod.GET, path = "/hellostrings") + @GetMapping("/hellostrings") public List getHelloStrings() { ArrayList hellos = new ArrayList<>(); hellos.add(HELLO_WORLD_1); @@ -622,7 +609,7 @@ class ValidFeignClientTests { return hellos; } - @RequestMapping(method = RequestMethod.GET, path = "/helloheaders") + @GetMapping("/helloheaders") public List getHelloHeaders(@RequestHeader(MYHEADER1) String myheader1, @RequestHeader(MYHEADER2) String myheader2) { ArrayList headers = new ArrayList<>(); @@ -631,23 +618,23 @@ class ValidFeignClientTests { return headers; } - @RequestMapping(method = RequestMethod.GET, path = "/helloheadersplaceholders") + @GetMapping("/helloheadersplaceholders") public String getHelloHeadersPlaceholders(@RequestHeader("myPlaceholderHeader") String myPlaceholderHeader) { return myPlaceholderHeader; } - @RequestMapping(method = RequestMethod.GET, path = "/helloparams") + @GetMapping("/helloparams") public List getParams(@RequestParam("params") List params) { return params; } - @RequestMapping(method = RequestMethod.GET, path = "/formattedparams") + @GetMapping("/formattedparams") public List getFormattedParams( @RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List params) { return params; } - @RequestMapping(method = RequestMethod.GET, path = "/noContent") + @GetMapping("/noContent") ResponseEntity noContent() { return ResponseEntity.noContent().build(); } @@ -662,7 +649,7 @@ class ValidFeignClientTests { throw new RuntimeException("always fails"); } - @RequestMapping(method = RequestMethod.GET, path = "/notFound") + @GetMapping("/notFound") ResponseEntity notFound() { return ResponseEntity.status(HttpStatus.NOT_FOUND).body((String) null); } @@ -676,17 +663,17 @@ class ValidFeignClientTests { return body; } - @RequestMapping(method = RequestMethod.GET, path = "/tostring") + @GetMapping("/tostring") String getToString(@RequestParam("arg") Arg arg) { return arg.toString(); } - @RequestMapping(method = RequestMethod.GET, path = "/tostring2") + @GetMapping("/tostring2") String getToString(@RequestParam("arg") OtherArg arg) { return arg.value; } - @RequestMapping(method = RequestMethod.GET, path = "/tostringcollection") + @GetMapping("/tostringcollection") Collection getToString(@RequestParam("arg") Collection args) { List result = new ArrayList<>(); for (OtherArg arg : args) { @@ -695,47 +682,40 @@ class ValidFeignClientTests { return result; } - @RequestMapping(method = RequestMethod.POST, path = "/singlePart", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/singlePart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String singlePart(@RequestPart("hello") String hello) { return hello; } - @RequestMapping(method = RequestMethod.POST, path = "/singlePojoPart", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/singlePojoPart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String singlePojoPart(@RequestPart("hello") Hello hello) { return hello.getMessage(); } - @RequestMapping(method = RequestMethod.POST, path = "/multipart", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world, @RequestPart("file") MultipartFile file) { return hello + world + file.getOriginalFilename(); } - @RequestMapping(method = RequestMethod.POST, path = "/multipartPojo", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartPojo", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world, @RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2, @RequestPart("file") MultipartFile file) { return hello + world + pojo1.getMessage() + pojo2.getMessage() + file.getOriginalFilename(); } - @RequestMapping(method = RequestMethod.POST, path = "/multipartNames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String multipartNames(HttpServletRequest request) throws Exception { return request.getParts().stream().map(Part::getName).collect(Collectors.joining(",")); } - @RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartFilenames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String multipartFilenames(HttpServletRequest request) throws Exception { return request.getParts().stream().map(Part::getSubmittedFileName).collect(Collectors.joining(",")); } - @RequestMapping(method = RequestMethod.POST, path = "/multipartPojosFiles", - consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + @PostMapping(path = "/multipartPojosFiles", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE) String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List pojos, @RequestPart("files") List files) { StringBuilder result = new StringBuilder(); @@ -751,6 +731,11 @@ class ValidFeignClientTests { return result.toString(); } + @PostMapping(path = "/form-urlencoded", consumes = APPLICATION_FORM_URLENCODED_VALUE) + Hello postFormUrlEncoded(Hello hello) { + return hello; + } + } public static class Hello {