So I have seen examples where a MultiPartFile type is passed in @RequestParam and not in @RequestBody. That seems to be a very usual way people suggest to consume a file content in a @RestController something like this

public ResponseEntity<String> submitFile(@RequestParam(value="file") MultipartFile file)

I am wondering how is it a good practice as the file data gets passed in the url. Why not pass it in @RequestBody instead?

So I changed the above code to something like this

public ResponseEntity<String> submitFile(@RequestBody MyCustomObj myObj)

myCustomObj is a pojo with just one field named file of type MultipartFile

The problem is that I only have swagger and postman to test it and when I use the @RequestBody approach, none of these would give me an option to upload a file as they would in case of passing MultipartFile in RequestParam.

Can someone please throw some more light on this and tell me the right way to do this?

2 Answers

0
Ahmed Abdelhady On Best Solutions

@RequestParam maps to query parameters, form data, and parts in multipart requests and not only query parameters as mentioned the offical docs. https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestParam.html

Files are not supposed to be sent as the request body serialized in JSON. What you should do instead is to use the content type "multipart/form-data" for your file uploads (as mentioned in the HTML 4 spec below) and in that case @RequestParam will be the appropriate annotation to use https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4

0
Paul Warren On

As an alternative and based on your comments I would recommend you take a look at the community project called Spring Content. This provides a resource abstraction over storage giving flexibility to where your content is stored and it injects the service and controller implementations for you so that you don't need to implement either yourself. Also, as you mentioned it might become important, Spring Content allows you to associate uploaded content with Spring Data entities too.

Adding it to your project would look something like this:

pom.xml (assuming maven. Spring boot starters also available)

    <!-- Java API -->
    <!-- just change this depdendency if you want to store somewhere else -->
    <dependency>
        <groupId>com.github.paulcwarren</groupId>
        <artifactId>spring-content-fs</artifactId>
        <version>0.8.0</version>
    </dependency>
    <!-- REST API -->
    <dependency>
        <groupId>com.github.paulcwarren</groupId>
        <artifactId>spring-content-rest</artifactId>
        <version>0.8.0</version>
    </dependency>

StoreConfig.java

@Configuration
@EnableFilesystemStores
@Import(RestConfiguration.class)
public class StoreConfig {

    @Bean
    FileSystemResourceLoader fileSystemResourceLoader() throws IOException {
        return new FileSystemResourceLoader(new File("/path/to/uploaded/files").getAbsolutePath());
    }

}

FileStore.java

  @StoreRestResource(path="files")
  public interface FileStore extends Store<String> {
  }

And that's it. The FileStore is essentially a generic Spring ResourceLoader. The spring-content-fs dependency will cause Spring Content to inject a filesystem-based implementation. The spring-content-rest dependency will cause Spring Content to also inject an implementation if an @Controller that forwards HTTP requests onto the methods of the FileStore service.

So you will now have a fully functional (POST, PUT, GET, DELETE) REST-based file service at /files that will use your FileStore to retrieve (and store) files in /path/to/uploaded/files on your server.

So:

curl --upload-file some-image.jpg /files/some-image.jpg

will upload some-image.jpg and store it in /path/to/uploaded/files on your server.

And:

curl /files/some-image.jpg

will retrieve it again.

HTH

The injected controller also supports video streaming too, in case that is useful.

Later on down the line if/when you want to associate content with a Spring Data entity all you would need to do is make your FileStore extend ContentStore instead of Store, type it to the Spring Data entity that you are associating with and add the Spring Content annotations to your entity, as follows:

//@StoreRestResource(path="files") <-- no longer required
public interface FileStore extends ContentStore<YourEntity, String> {
}

@Entity
public class YourEntity {
   @Id
   ...

   @ContentId
   private String contentId;

   @ContentLength
   private String contentLen;

   @MimeType
   private String contentType;
}

And that's it. As you might expect your REST endpoints change so that you now address content using the same URI space as your Spring Data entity. So:

curl --upload-file some-image.jpg /yourEntities/{yourEntityId}

will upload some-image.jpg, store it in /path/to/uploaded/files on your server and associate it with the entity yourEntityId.

And:

curl /yourEntities/{yourEntityId}

will retrieve it again.

Multiple pieces of content can be associated by using conventional @OneToOne and @OneToMany associations and are reflected accordingly in the URI in a (hopefully) intuitive way.

HTH