Sunil Kumar
Sunil Kumar

Reputation: 5647

Spring MVC Multipart Request with JSON

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?

Upvotes: 105

Views: 176504

Answers (6)

JPRLCol
JPRLCol

Reputation: 200

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);
            });

Upvotes: 2

Hardik
Hardik

Reputation: 215

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.');
  })
}

Upvotes: 1

Drunken Daddy
Drunken Daddy

Reputation: 7991

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);
};

Upvotes: 1

Sunil Kumar
Sunil Kumar

Reputation: 5647

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--

Upvotes: 252

mohi
mohi

Reputation: 1093

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

Upvotes: 15

Vaelyr
Vaelyr

Reputation: 3176

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.

Upvotes: 0

Related Questions