7

When make a post request from one microservice to another using feign client of spring cloud netflix, I get the following error in Postman :

{
"timestamp": 1506933777413,
"status": 500,
"error": "Internal Server Error",
"exception": "feign.codec.EncodeException",
"message": "Could not write JSON: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile[\"inputStream\"]->java.io.FileInputStream[\"fd\"])",
"path": "/attachments"
}

And my eclipse console shows the following exception :

com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile["inputStream"]->java.io.FileInputStream["fd"]) at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:284) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.SerializerProvider.mappingException(SerializerProvider.java:1110) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.SerializerProvider.reportMappingProblem(SerializerProvider.java:1135) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:69) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:32) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:689) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:689) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1429) ~[jackson-databind-2.8.9.jar:2.8.9] at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:951) ~[jackson-databind-2.8.9.jar:2.8.9]

UPDATE 1

This is my feign interface :

@FeignClient(name="attachment-service", fallback=AttachmentHystrixFallback.class)
public interface AttachmentFeignClient {

@RequestMapping("upload")
void upload(@RequestPart(name="file") MultipartFile file, @RequestParam(name="attachableId") Long attachableId, 
        @RequestParam(name="className") String className, @RequestParam(name="appName") String appName);

And this is the main microservice controller :

@RestController
public class AttachmentController implements Serializable {

/**
 * 
 */
private static final long serialVersionUID = -4431842080646836475L;

@Autowired
AttachmentService attachmentService;

@RequestMapping(value = "attachments", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void upload(@RequestPart MultipartFile file, @RequestParam Long attachableId, @RequestParam String className, @RequestParam String appName) throws Exception {
    attachmentService.uploadFile(file, attachableId, className, appName);
}

}

I'm certainly missing some kind of serializer here
Any suggestion would be appreciated !
Thanks

ZiOS
  • 433
  • 2
  • 5
  • 14
  • are the pojos on those 2 microservices identical? If their fields and name matches, you should not need any serializer. Show a snipped of your code with rest defined in one service, and the same method in your feinclient interface – mlecz Oct 02 '17 at 08:52
  • @mlecz yes all the pojos are located in a starter which is integrated in both microservices – ZiOS Oct 02 '17 at 08:54
  • @mlecz take a look at the update 1 please – ZiOS Oct 02 '17 at 09:15
  • those 2 look simillar. No idea how to help you. I see you updated this post again, but before update I saw 2 methods linked to attachements url, one get, one post. Maybe try adding RequestMapping.get to feign client? – mlecz Oct 02 '17 at 09:21
  • I found a solution by adding some dependencies for feign form .. – ZiOS Oct 02 '17 at 09:49

3 Answers3

15

After few days searching a solution I found this. You should start to add feign form for spring dependency :

<dependency>
   <groupId>io.github.openfeign.form</groupId>
   <artifactId>feign-form-spring</artifactId>
   <version>3.3.0</version>
</dependency

Then your feign client need this spring form encoder :

@FeignClient(
    name="attachment-service",  
    configuration = {AttachmentFeignClient.MultipartSupportConfig.class}
    fallback=AttachmentHystrixFallback.class)
public interface AttachmentFeignClient {

@RequestMapping(value= {"upload"}, consumes = {"multipart/form-data"})
void upload(
    @RequestPart(name="file") MultipartFile file, 
    @RequestParam(name="attachableId") Long attachableId, 
    @RequestParam(name="className") String className,
    @RequestParam(name="appName") String appName);

 public class MultipartSupportConfig {
    @Bean
    @Primary
    @Scope("prototype")
    public Encoder feignFormEncoder() {
        return new SpringFormEncoder();
    }
  }
}

I hope it will help someone.

Zounadire
  • 1,496
  • 2
  • 18
  • 38
Martin Choraine
  • 2,296
  • 3
  • 20
  • 37
  • Hey Martin, I did the same config as you but I am getting "Required request part 'file' is not present" have you seen this error? – djeison Dec 08 '18 at 13:55
  • because of the upload method set a parameter "file" , so you must set the file filed named "file" – bp zhang May 23 '19 at 02:42
9

Update 13/01/2020
Configure FeignClient to handle MultiPartFile with @RequestPart
Note that this approach is suitable only if you have one RequestPart that represents the whole body. Having multiple RequestPart is another problem with FeignClient.


The configuration of the feignclient is similar to what @marting-choraine did, but by adding the extra Encoder Configuration in the FeignConfigurationClass.

So with the same example that I provide below, instead of implementing a custom mapper, you will need to do something like this

@Configuration
@EnableFeignClients(basePackages = "YourPackage")
public class FeignConfiguration {

  @Bean
  public Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
      return new SpringFormEncoder(new SpringEncoder(messageConverters));
  }
}

TL;DR
Transform your MultiPartFile to MultiValueMap. See example below


The answer mentioned by @martin-choraine is the proper and the best answer to have your FeignClient method signature, the same as the actual endpoint signature that you are trying to call. However, there is a way around that does not require you to define a FormEncoder or add any extra dependency because in some applications you are not allowed to do that (enterprise shit); all what you need is to transform your MultipartFile to a MultiValueMap and it will perfectly work as the standard encoder will be able to serialize it.


Actual EndPoint to be called

@PostMapping(path = "/add", consumes = MULTIPART_FORM_DATA_VALUE, produces = APPLICATION_JSON_VALUE)
 public MyResponseObject add(@RequestParam(name = "username") String username,
                             @RequestPart(name = "filetoupload") MultipartFile file) {
              Do something
}

The POST method in your FeignClient should look like this

@PostMapping(path = "/myApi/add", consumes = MULTIPART_FORM_DATA_VALUE, 
              produces = APPLICATION_JSON_VALUE)
 public MyResponseObject addFile(@RequestParam(name = "username") String username,
                           @RequestPart(name = "filetoupload") MultiValueMap<String, Object> file);

Then when you call the addFile you should provide MultiValueMap like this

public MyResponseObject addFileInAnotherEndPoint(String username, MultipartFile file) throws IOException {

    MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
    ByteArrayResource contentsAsResource = new ByteArrayResource(file.getBytes()) {
        @Override
        public String getFilename() {
            return file.getOriginalFilename();
        }
    };
    multiValueMap.add("filetoupload", contentsAsResource);
    multiValueMap.add("fileType", file.getContentType());
    return this.myFeignClient.addFile(username, multiValueMap);
}
Ahmed Kareem
  • 351
  • 1
  • 4
  • 6
  • any idea how to implement the same for Multiple files? I checked your comments on GitHub too. I tried `FeignSpringFormEncoder` but it now throws `com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.io.BufferedInputStream` . – Mohammed Idris Nov 18 '19 at 12:40
  • @MohammedIdris I actually never been a situation where I need to send a list of files. However, when I was looking for my problem there was this library [FormEncoder](https://github.com/OpenFeign/feign-form). Also, this stackoverflow question [here](https://stackoverflow.com/questions/42212557/uploading-a-list-of-multipartfile-with-spring-4-resttemplate-java-client-rest) may help you. – Ahmed Kareem Nov 20 '19 at 22:29
  • I tried this but it's not working,found another [FeignEncoder](https://github.com/pcan/feign-client-test/blob/master/src/main/java/it/pcan/test/feign/client/FeignSpringFormEncoder.java) which didn't encode the request with bodyType. I have tried with restTemplate which requires more code to be written as compared to Feign, however, I have the other microservice(the one that takes images and processes them) on a eureka discovery. If I can somehow connect RestTemplate via eureka it would be enough for now. I do not want to mention port number and url in the url param of rest (if that's possible) – Mohammed Idris Nov 21 '19 at 07:17
  • By not working I mean the `encode` method in `Encoder` class throws `NPE`. I could post the error in **github openFeign** but i decided to take restTemplate approach with async calls for each image which I realised is a better approach. I tried to resolve feign error but due to lack of prior experience and fewer examples on net I could not resolve the error. I tried commenting on other github issues but no response yet. I am new to microservices, if anyone could point me to example(s) where eureka client name is used in restTemplate it would be great. Thanks in advance. – Mohammed Idris Nov 21 '19 at 07:25
  • For your question regarding "Resolving services identifiers from Eureka client", I think what you need is "load balancing" specifically @LoadBalanced from spring cloud. Look at this stackOverFlow question[here](https://stackoverflow.com/questions/39587317/difference-between-ribbonclient-and-loadbalanced) explaining LoadBalanced vs RibbonClient, dig more into the details and you will hopefully get a good answer. – Ahmed Kareem Dec 03 '19 at 17:59
1

i have added consumes = MediaType.MULTIPART_FORM_DATA_VALUE in the post and it works for me. this is my feign client method and in the front i worked with formdata

@PostMapping(path=Urls.UPLOAD_FILE_IN_LIBELLE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE )
    public void uploadFileInLibelle(
            @RequestParam String processus,
            @RequestParam String level0Name,
            @RequestParam String nomFichier,
            @RequestParam String nomLibelle,
            @RequestParam String anneeFolderName,
            @RequestParam String semaineFolderName,
            @RequestPart   MultipartFile fichier);

this is my angular frontent

public uploadFiles(
        nomFichier: string,
        nomLibelle: string,
        processus: string,
        level0Name: string,
        semaineFolderName: string,
        anneeFolderName: string,
        byte: File
    ): Observable<any> {
        const formData = new FormData();
        formData.set('processus', processus);
        formData.set('level0Name', level0Name);
        formData.set('nomLibelle', nomLibelle);
        formData.set('anneeFolderName', anneeFolderName);
        formData.set('semaineFolderName', semaineFolderName);
        formData.set('nomFichier', nomFichier);
        formData.set('fichier', byte);

        return this.httpClient.post(this.urlUploadFile, formData);
    }
fatma
  • 215
  • 1
  • 4
  • 13