Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Feign multipart with Json request part

I have Feign client in one service with a method

@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
 MyDto uploadDocument(@RequestPart("file") MultipartFile file,
                               @RequestPart("myDto") String myDto);

I have a controller in another service

@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<MyDto> uploadDocument(@RequestParam("file") MultipartFile file,
                                                      @RequestPart("myDto") MyDto myDto) {
.... some code here
    }

The issue I faced is that Feign sends myDto with Content-type : text/plain and I have HttpMediaTypeNotSupportedException

Is it possible to send @RequestPart("myDto") String myDto with Content-type : application/json ?

expected Raw request:

----------------------------boundary
Content-Disposition: form-data; name="file"; filename="fileName"
<file>
----------------------------boundary
Content-Disposition: form-data; name="myDto"
**Content-Type: application/json**
{"myDto": ""}

Current raw request:

----------------------------boundary
Content-Disposition: form-data; name="file"; filename="fileName"
<file>
----------------------------boundary
Content-Disposition: form-data; name="myDto"
**Content-Type: text/plain**
{"myDto": ""}
like image 479
Illia Avatar asked Mar 20 '26 14:03

Illia


2 Answers

You need to define bean JsonFormWriter in your feign client's configuration.

Here is an example of the client:

@FeignClient(
        name = "my-client",
        configuration = MyClientConfiguration.class
)
public interface MyClient {

    @PostMapping(value = "/file/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    void uploadFile(@RequestPart("request") MyFileUploadRequest request,
                                        @RequestPart("file") MultipartFile file);

}

public class MyClientConfiguration {

    @Bean
    JsonFormWriter jsonFormWriter() {
        return new JsonFormWriter();
    }
}

And an example of the controller:

@RestController
public class FileUploadApi {

    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public void uploadFile(
            @RequestPart("request") MyFileUploadRequest request,
            @RequestPart("file") MultipartFile file) {
    }

This feature was added in scope of this PR: https://github.com/spring-cloud/spring-cloud-openfeign/pull/314

like image 169
Anastasiia Smirnova Avatar answered Mar 23 '26 05:03

Anastasiia Smirnova


Managed to solve this by replacing the feign-form PojoWriter. By default it's serializing each field of an object as a separate part.

    @Bean
    public Encoder feignEncoder () {
        return new MyFormEncoder(objectMapper, new SpringEncoder(messageConverters));
    }
public class MyFormEncoder extends SpringFormEncoder {
    /**
     * Constructor with specified delegate encoder.
     *
     * @param delegate  delegate encoder, if this encoder couldn't encode object.
     */
    public MyFormEncoder(ObjectMapper objectMapper, Encoder delegate) {
        super(delegate);

        val processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART);
        processor.addFirstWriter(new MyPojoWriter(objectMapper));
    }
}
@FieldDefaults(level = PRIVATE, makeFinal = true)
public class MyPojoWriter extends AbstractWriter {

    private ObjectMapper objectMapper;

    public MyPojoWriter(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public boolean isApplicable(Object object) {
        return isUserPojo(object);
    }

    @Override
    protected void write(Output output, String key, Object value) throws EncodeException {
        var data = "";
        try {
            data = objectMapper.writeValueAsString(value);
        } catch (JsonProcessingException e) {
        }
        val string = new StringBuilder()
                .append("Content-Disposition: form-data; name=\"").append(key).append('"').append(CRLF)
                .append("Content-Type: application/json; charset=").append(output.getCharset().name()).append(CRLF)
                .append(CRLF)
                .append(data)
                .toString();

        output.write(string);
    }

    private boolean isUserPojo(@NonNull Object object) {
        val type = object.getClass();
        val typePackage = type.getPackage();
        return typePackage != null && typePackage.getName().startsWith("com.my-package.");
    }
}
like image 44
Maja Stamenic Avatar answered Mar 23 '26 04:03

Maja Stamenic



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!