Search code examples
spring-cloud-feign

Send MultiValueMap as MultiPartFormData in Feign Client


I am trying to convert the below kotlin code from RestTemplate to Feign client. The rest template code sends multiValueMap as request with content-type header multipart/form-data and consumes JSON object as response.

RestTemplate Code:

    var headers = HttpHeaders()
    headers.contentType = MediaType.MULTIPART_FORM_DATA
    headers.add("custom-header", "value")
    val body: MultiValueMap<String, Any> = LinkedMultiValueMap()
    body.add("field1", "value1")
    body.add("field2", "value2")
    val requestEntity = HttpEntity(body, headers)
    return restTemplate.postForEntity("https://enmf7tx8y37x.x.pipedream.net/", requestEntity, Object::class.java)

In this case the request is sent as below:

Headers:

Host: enmf7tx8y37x.x.pipedream.net
X-Amzn-Trace-Id: Root=1-6303ecb2-19a833a044ab3bf83f74f256
Content-Length: 342
Accept: application/xml, text/xml, application/json, application/*+xml, application/*+json
Content-Type: multipart/form-data;boundary=_MtEGFIF4XK_aOU8QsXstQuCliV1-llj
custom-header: value
X-B3-TraceId: a67561ec329f9a16
X-B3-SpanId: a6cc94e403bfe318
X-B3-ParentSpanId: a67561ec329f9a16
X-B3-Sampled: 1
User-Agent: Apache-HttpClient/4.5.13 (Java/17.0.3)
Accept-Encoding: gzip,deflate

Body:

 --_MtEGFIF4XK_aOU8QsXstQuCliV1-llj
    Content-Disposition: form-data; name="field1"
    Content-Type: text/plain;charset=UTF-8
    Content-Length: 6
    
    value1
    --_MtEGFIF4XK_aOU8QsXstQuCliV1-llj
    Content-Disposition: form-data; name="field2"
    Content-Type: text/plain;charset=UTF-8
    Content-Length: 6
    
    value2
    --_MtEGFIF4XK_aOU8QsXstQuCliV1-llj--

I tried to do the same in Feign client:

code:

/*val headers = HttpHeaders()
    headers.contentType = MediaType.MULTIPART_FORM_DATA
    headers.add("custom-header", "value")*/
    val body: MultiValueMap<String, Any> = LinkedMultiValueMap()
    body.add("field1", "value1")
    body.add("field2", "value2")
    val result = testClient.test("value", body)

Feign Client:

@FeignClient(
  value = "testClient",
  url = "https://enmf7tx8y37x.x.pipedream.net/"
)
interface TestClient {
  @PostMapping(
    consumes = [MediaType.MULTIPART_FORM_DATA_VALUE],
    produces = [MediaType.APPLICATION_JSON_VALUE]
  )
  fun test(
    @RequestHeader(value = "custom-header") customHeader: String,
    @RequestPart("request") request: MultiValueMap<String, Any>
  ): ResponseEntity<Object>

}

The header are fine but no value present in the body.

Header:

Host: enmf7tx8y37x.x.pipedream.net
X-Amzn-Trace-Id: Root=1-6303ef0f-78c869881a5b27d0707eab9e
Content-Length: 17
Accept: application/json
Authorization: Basic aHlwb2xhYjp0ZXN0c211cmY=
Content-Type: multipart/form-data; charset=UTF-8; boundary=182c75dd399
custom-header: value
X-B3-TraceId: 2989eb4f12e3d417
X-B3-SpanId: 23414bcdf365784c
X-B3-ParentSpanId: 2989eb4f12e3d417
X-B3-Sampled: 1
User-Agent: Java/17.0.3

Body:

--182c75dd399--

I had to add consumes value as multipart/form-data instead of json to get the right header values for Accept and Content-Type.

How can I populate the request using Feign client? If the @RequestPart is String then the value is sent in the body but any other data type like multiValueMap, byteArray, etc were not working


Solution

  • In feign client you cannot use MultiValueMap directly. You have to use MultipartFile datatype for bytearray and for the remaining metadata fields you need to mention each one as a separate argument in the method. Then FeignClient will generate the same request like the one you showed when using RestTemplate.

    import org.springframework.cloud.openfeign.FeignClient
    import org.springframework.web.bind.annotation.PostMapping
    import org.springframework.http.MediaType
    import org.springframework.http.ResponseEntity
    import org.springframework.web.bind.annotation.RequestHeader
    import org.springframework.web.bind.annotation.RequestPart
    import org.springframework.web.multipart.MultipartFile
    
        @FeignClient(
          value = "testClient",
          url = "https://enmf7tx8y37x.x.pipedream.net/"
        )
        interface TestClient {
          @PostMapping(
            consumes = [MediaType.MULTIPART_FORM_DATA_VALUE],
            produces = [MediaType.APPLICATION_JSON_VALUE]
          )
          fun test(
            @RequestHeader(value = "custom-header") customHeader: String,
            @RequestPart(name = "file") file: MultipartFile,
            @RequestPart(name = "field1") field1: String
          ): ResponseEntity<Object>
        
        }
    

    code sample for How to create MultiPartFile:

    import org.springframework.mock.web.MockMultipartFile
    
    //val multipartFile: MultipartFile = MockMultipartFile("filename", byteArray)
    val multipartFile: MultipartFile = MockMultipartFile("filename", "filename", "content type like application/pdf", byteArray)