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.
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.
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.
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.
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.
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.
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>
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); } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With