Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot controller - Upload Multipart and JSON to DTO

I want to upload a file inside a form to a Spring Boot API endpoint.

The UI is written in React:

export function createExpense(formData) {   return dispatch => {     axios.post(ENDPOINT,       formData,        headers: {         'Authorization': //...,         'Content-Type': 'application/json'       }       ).then(({data}) => {         //...       })       .catch(({response}) => {         //...       });     }; }    _onSubmit = values => {     let formData = new FormData();     formData.append('title', values.title);     formData.append('description', values.description);     formData.append('amount', values.amount);     formData.append('image', values.image[0]);     this.props.createExpense(formData);   } 

This is the java side code:

@RequestMapping(path = "/{groupId}", method = RequestMethod.POST) public ExpenseSnippetGetDto create(@RequestBody ExpensePostDto expenseDto, @PathVariable long groupId, Principal principal, BindingResult result) throws IOException {    //.. } 

But I get this exception on the Java side:

org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=----WebKitFormBoundaryapHVvBsdZYc6j4Af;charset=UTF-8' not supported 

How should I resolve this issue? The similar API endpoints and JavaScript side code is already working.

Note

I've seen a solution where it suggests that the request body should have 2 attributes: one which the JSON section goes under, another for the image. I'd like to see if it is possible to have it automatically converted to DTO.


Update 1

The upload payload sent by the client should be converted to the following DTO:

public class ExpensePostDto extends ExpenseBaseDto {      private MultipartFile image;      private String description;      private List<Long> sharers;  } 

So you can say it's a mix of JSON and multipart.


Solution

The solution to the problem is to use FormData on the front-end and ModelAttribute on the backend:

@RequestMapping(path = "/{groupId}", method = RequestMethod.POST,         consumes = {"multipart/form-data"}) public ExpenseSnippetGetDto create(@ModelAttribute ExpensePostDto expenseDto, @PathVariable long groupId, Principal principal) throws IOException {    //... } 

and on the front-end, get rid of Content-Type as it should be determined by the browser itself, and use FormData (standard JavaScript). That should solve the problem.

like image 282
Arian Avatar asked Apr 15 '18 18:04

Arian


People also ask

How do you send the multipart file and JSON data to spring boot?

To pass the Json and Multipart in the POST method we need to mention our content type in the consume part. And we need to pass the given parameter as User and Multipart file. Here, make sure we can pass only String + file not POJO + file. Then convert the String to Json using ObjectMapper in Service layer.

What is @RequestPart?

Using @RequestPartThis annotation associates a part of a multipart request with the method argument, which is useful for sending complex multi-attribute data as payload, e.g., JSON or XML.


2 Answers

I had created a similar thing using pure JS and Spring Boot. Here is the Repo. I'm sending an User object as JSON and a File as part of the multipart/form-data request.

The relevant snippets are below

The Controller code

@RestController public class FileUploadController {      @RequestMapping(value = "/upload", method = RequestMethod.POST, consumes = { "multipart/form-data" })     public void upload(@RequestPart("user") @Valid User user,             @RequestPart("file") @Valid @NotNull @NotBlank MultipartFile file) {             System.out.println(user);             System.out.println("Uploaded File: ");             System.out.println("Name : " + file.getName());             System.out.println("Type : " + file.getContentType());             System.out.println("Name : " + file.getOriginalFilename());             System.out.println("Size : " + file.getSize());     }      static class User {         @NotNull         String firstName;         @NotNull         String lastName;          public String getFirstName() {             return firstName;         }          public void setFirstName(String firstName) {             this.firstName = firstName;         }          public String getLastName() {             return lastName;         }          public void setLastName(String lastName) {             this.lastName = lastName;         }          @Override         public String toString() {             return "User [firstName=" + firstName + ", lastName=" + lastName + "]";         }      } } 

The HTML and JS code

<html>     <head>     <script>         function onSubmit() {              var formData = new FormData();              formData.append("file", document.forms["userForm"].file.files[0]);             formData.append('user', new Blob([JSON.stringify({                 "firstName": document.getElementById("firstName").value,                 "lastName": document.getElementById("lastName").value             })], {                     type: "application/json"                 }));             var boundary = Math.random().toString().substr(2);             fetch('/upload', {                 method: 'post',                 body: formData             }).then(function (response) {                 if (response.status !== 200) {                     alert("There was an error!");                 } else {                     alert("Request successful");                 }             }).catch(function (err) {                 alert("There was an error!");             });;         }     </script> </head>  <body>     <form name="userForm">         <label> File : </label>         <br/>         <input name="file" type="file">         <br/>         <label> First Name : </label>         <br/>         <input id="firstName" name="firstName" />         <br/>         <label> Last Name : </label>         <br/>         <input id="lastName" name="lastName" />         <br/>         <input type="button" value="Submit" id="submit" onclick="onSubmit(); return false;" />     </form> </body>     </html> 
like image 44
GSSwain Avatar answered Sep 30 '22 18:09

GSSwain


Yes, you can simply do it via wrapper class.

1) Create a Class to hold form data:

public class FormWrapper {     private MultipartFile image;     private String title;     private String description; } 

2) Create an HTML form for submitting data:

<form method="POST" enctype="multipart/form-data" id="fileUploadForm" action="link">     <input type="text" name="title"/><br/>     <input type="text" name="description"/><br/><br/>     <input type="file" name="image"/><br/><br/>     <input type="submit" value="Submit" id="btnSubmit"/> </form> 

3) Create a method to receive form's text data and multipart file:

@PostMapping("/api/upload/multi/model") public ResponseEntity<?> multiUploadFileModel(@ModelAttribute FormWrapper model) {     try {         // Save as you want as per requiremens         saveUploadedFile(model.getImage());         formRepo.save(mode.getTitle(), model.getDescription());     } catch (IOException e) {         return new ResponseEntity<>(HttpStatus.BAD_REQUEST);     }      return new ResponseEntity("Successfully uploaded!", HttpStatus.OK); } 

4) Method to save file:

private void saveUploadedFile(MultipartFile file) throws IOException {     if (!file.isEmpty()) {         byte[] bytes = file.getBytes();         Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());         Files.write(path, bytes);     } } 
like image 108
UsamaAmjad Avatar answered Sep 30 '22 18:09

UsamaAmjad