Spring MVC Multipart Request with JSON
Asked Answered
B

6

105

I want to post a file with some JSON data using Spring MVC. So I've developed a rest service as

@RequestMapping(value = "/servicegenerator/wsdl", method = RequestMethod.POST,consumes = { "multipart/mixed", "multipart/form-data" })
@ResponseBody
public String generateWSDLService(@RequestPart("meta-data") WSDLInfo wsdlInfo,@RequestPart("file") MultipartFile file) throws WSDLException, IOException,
        JAXBException, ParserConfigurationException, SAXException, TransformerException {
    return handleWSDL(wsdlInfo,file);
}

When I send a request from the rest client with content-Type = multipart/form-data or multipart/mixed, I get the next exception: org.springframework.web.multipart.support.MissingServletRequestPartException

Can anyone help me in solving this issue?

Can I use @RequestPart to send both Multipart and JSON to a server?

Bab answered 24/1, 2014 at 9:50 Comment(2)
Have you specified a org.springframework.web.multipart.commons.CommonsMultipartResolver in your servlet context?Hydantoin
yes,it is added in my spring.xml. <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="300000000"/> </bean>Bab
B
252

This is how I implemented Spring MVC Multipart Request with JSON Data.

Multipart Request with JSON Data (also called Mixed Multipart):

Based on RESTful service in Spring 4.0.2 Release, HTTP request with the first part as XML or JSON formatted data and the second part as a file can be achieved with @RequestPart. Below is the sample implementation.

Java Snippet:

Rest service in Controller will have mixed @RequestPart and MultipartFile to serve such Multipart + JSON request.

@RequestMapping(value = "/executesampleservice", method = RequestMethod.POST,
    consumes = {"multipart/form-data"})
@ResponseBody
public boolean executeSampleService(
        @RequestPart("properties") @Valid ConnectionProperties properties,
        @RequestPart("file") @Valid @NotNull @NotBlank MultipartFile file) {
    return projectService.executeSampleService(properties, file);
}

Front End (JavaScript) Snippet:

  1. Create a FormData object.

  2. Append the file to the FormData object using one of the below steps.

    1. If the file has been uploaded using an input element of type "file", then append it to the FormData object. formData.append("file", document.forms[formName].file.files[0]);
    2. Directly append the file to the FormData object. formData.append("file", myFile, "myfile.txt"); OR formData.append("file", myBob, "myfile.txt");
  3. Create a blob with the stringified JSON data and append it to the FormData object. This causes the Content-type of the second part in the multipart request to be "application/json" instead of the file type.

  4. Send the request to the server.

  5. Request Details:
    Content-Type: undefined. This causes the browser to set the Content-Type to multipart/form-data and fill the boundary correctly. Manually setting Content-Type to multipart/form-data will fail to fill in the boundary parameter of the request.

Javascript Code:

formData = new FormData();

formData.append("file", document.forms[formName].file.files[0]);
formData.append('properties', new Blob([JSON.stringify({
                "name": "root",
                "password": "root"                    
            })], {
                type: "application/json"
            }));

Request Details:

method: "POST",
headers: {
         "Content-Type": undefined
  },
data: formData

Request Payload:

Accept:application/json, text/plain, */*
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryEBoJzS3HQ4PgE1QB

------WebKitFormBoundaryvijcWI2ZrZQ8xEBN
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: application/txt


------WebKitFormBoundaryvijcWI2ZrZQ8xEBN
Content-Disposition: form-data; name="properties"; filename="blob"
Content-Type: application/json


------WebKitFormBoundaryvijcWI2ZrZQ8xEBN--
Bab answered 7/8, 2014 at 13:2 Comment(13)
Hi, what is ConnectionProperties, just a POJO?Easygoing
Hi Liu, Yes Connection properties is just a POJO class.Our aim is to take some pojo object and multipart in the rest request.Bab
Nicely done. I had to use processData: false, contentType: false with JQuery $ajax()Catholicon
If you ever looking to do a curl to construct this request payload, refer this answer which has multipart curl commandTruditrudie
@SunilKumar, If i need to give file upload as optional..?with form Data.How can i do this. Because if image is not selected am getting Required request part file is not presentShrovetide
It can be off topic question. Do you have RAML example for request with parts?Saldivar
For me the "new Blob([JSON.stringify(...)]" part did it.. I had everything else in place. Thx.Adjoin
With angular 5, the line ...headers: { "Content-Type": undefined } Caused issues with the HttpClient from Angular/Common. Instead, I just did not define the Content Type and it worked!Jayjaycee
Using this method I'm getting an error because Spring is expecting a class, not an interface. Any ideas?Areta
What about uploading Array of "connectionProperties and file" in one request?Leastwise
What happens if we change consumes to 'multipart/mixed' ?Fingertip
@SunilKumar did you have to specify Converter for ConnectionProperties ? If I use a pojo like shown above for ConnectionProperties I get ... HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported If I change the POJO to String it works. So its not clear how the conversion to the POJO is happening ?Marchpane
Just to make this clear: The @NotBlank annotation on the MultipartFile method parameter won't actually check if the file is empty. It is still possible to upload documents with 0 bytes.Pincus
L
15

This must work!

client (angular):

$scope.saveForm = function () {
      var formData = new FormData();
      var file = $scope.myFile;
      var json = $scope.myJson;
      formData.append("file", file);
      formData.append("ad",JSON.stringify(json));//important: convert to JSON!
      var req = {
        url: '/upload',
        method: 'POST',
        headers: {'Content-Type': undefined},
        data: formData,
        transformRequest: function (data, headersGetterFunction) {
          return data;
        }
      };

Backend-Spring Boot:

@RequestMapping(value = "/upload", method = RequestMethod.POST)
    public @ResponseBody
    Advertisement storeAd(@RequestPart("ad") String adString, @RequestPart("file") MultipartFile file) throws IOException {

        Advertisement jsonAd = new ObjectMapper().readValue(adString, Advertisement.class);
//do whatever you want with your file and jsonAd
Litha answered 5/5, 2015 at 2:18 Comment(0)
M
2

You can also use the next way a list List<MultipartFile> and @RequestPart("myObj") as parameters in your method inside a @RestController


    @PostMapping()
    @ResponseStatus(HttpStatus.CREATED)
    public String create(@RequestPart("file") List<MultipartFile> files, @RequestPart("myObj") MyJsonDTOClass myObj) throws GeneralSecurityException, IOException {
        // your code

    }

and in the axios side with a bit of react:

            const jsonStr = JSON.stringify(myJsonObj);
            const blob = new Blob([jsonStr], {
                type: 'application/json'
            });

            let formData = new FormData();
            formData.append("myObj",blob );
            formData.append("file", this.state.fileForm); // check your control 
            let url = `your url`            
            let method = `post`
            let headers =
                {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                };
            }

            axios({
                method,
                url,
                data: formData,
                headers
            }).then(res => {
                console.log(res);
                console.log(res.data);
            });

Mordvin answered 6/9, 2022 at 3:5 Comment(0)
M
1

We've seen in our projects that a post request with JSON and files is creating a lot of confusion between the frontend and backend developers, leading to unnecessary wastage of time.

Here's a better approach: convert file bytes array to Base64 string and send it in the JSON.

public Class UserDTO {
    private String firstName;
    private String lastName;
    private FileDTO profilePic; 
}

public class FileDTO {
    private String base64;
    // just base64 string is enough. If you want, send additional details
    private String name;
    private String type;
    private String lastModified;
}

@PostMapping("/user")
public String saveUser(@RequestBody UserDTO user) {
    byte[] fileBytes = Base64Utils.decodeFromString(user.getProfilePic().getBase64());
    ....
}

JS code to convert file to base64 string:

var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {

  const userDTO = {
    firstName: "John",
    lastName: "Wick",
    profilePic: {
      base64: reader.result,
      name: file.name,
      lastModified: file.lastModified,
      type: file.type
    }
  }
  
  // post userDTO
};
reader.onerror = function (error) {
  console.log('Error: ', error);
};
Matchless answered 23/7, 2020 at 22:30 Comment(0)
P
1

For Angular2+ users. Try to send JSON payload in a mixed part request as below.

formData.append("jsonPayload", new Blob([JSON.stringify(json)], {
  type: "application/json"
}));

Given below complete function.

submit() {
    const formData = new FormData();
    formData.append('file', this.myForm.get('fileSource').value);
    var json = {
        "key":"value"
    };

  formData.append("jsonPayload", new Blob([JSON.stringify(json)], {
    type: "application/json"
  }));

  this.http.post('http://localhost:8080/api/mixed-part-endpoint', formData)
  .subscribe(res => {
    console.log(res);
    alert('Uploaded Successfully.');
  })
}
Puny answered 9/7, 2021 at 18:30 Comment(0)
P
0

As documentation says:

Raised when the part of a "multipart/form-data" request identified by its name cannot be found.

This may be because the request is not a multipart/form-data either because the part is not present in the request, or because the web application is not configured correctly for processing multipart requests -- e.g. no MultipartResolver.

Psychiatry answered 24/1, 2014 at 10:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.