Spring Boot Controller - Upload Multipart and JSON to Dto

Spring Boot controller - Upload Multipart and JSON to DTO

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

How to send the Multipart file and json data to spring boot

You cat use @RequestParam and Converter for JSON objects

simple example :

@SpringBootApplication
public class ExampleApplication {

public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}

@Data
public static class User {
private String name;
private String lastName;
}

@Component
public static class StringToUserConverter implements Converter<String, User> {

@Autowired
private ObjectMapper objectMapper;

@Override
@SneakyThrows
public User convert(String source) {
return objectMapper.readValue(source, User.class);
}
}

@RestController
public static class MyController {

@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file,
@RequestParam("user") User user) {
return user + "\n" + file.getOriginalFilename() + "\n" + file.getSize();
}

}

}

and postman:
Sample Image

UPDATE
apache httpclient 4.5.6 example:

pom.xml dependency:

    <dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.6</version>
</dependency>

<!--dependency for IO utils-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>

service will be run after application fully startup, change File path for your file

@Service
public class ApacheHttpClientExample implements ApplicationRunner {

private final ObjectMapper mapper;

public ApacheHttpClientExample(ObjectMapper mapper) {
this.mapper = mapper;
}

@Override
public void run(ApplicationArguments args) {
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
File file = new File("yourFilePath/src/main/resources/foo.json");
HttpPost httpPost = new HttpPost("http://localhost:8080/upload");

ExampleApplication.User user = new ExampleApplication.User();
user.setName("foo");
user.setLastName("bar");
StringBody userBody = new StringBody(mapper.writeValueAsString(user), MULTIPART_FORM_DATA);
FileBody fileBody = new FileBody(file, DEFAULT_BINARY);

MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
entityBuilder.addPart("user", userBody);
entityBuilder.addPart("file", fileBody);
HttpEntity entity = entityBuilder.build();
httpPost.setEntity(entity);

HttpResponse response = client.execute(httpPost);
HttpEntity responseEntity = response.getEntity();

// print response
System.out.println(IOUtils.toString(responseEntity.getContent(), UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}

}

console output will look like below:

ExampleApplication.User(name=foo, lastName=bar)
foo.json
41

Spring MVC POST request with dto that contains multipart files and other dtos

This seems to be an issue with how the springdoc-openapi-ui builds the form-data request. I was able to reproduce this and noticed that it sends a multipart-request like (intercepted through browser's dev-tools):

-----------------------------207598777410513073071314493349
Content-Disposition: form-data; name="specializationDto"\r\n\r\n{\r\n "something": "someValue"\r\n}

-----------------------------207598777410513073071314493349
Content-Disposition: form-data; name="files"; filename="somefile.txt"
Content-Type: application/octet-stream

<content>

-----------------------------207598777410513073071314493349
Content-Disposition: form-data; name="files"; filename="somefile.txt"
Content-Type: application/octet-stream

<content>

With that payload Spring is not able to deserialize the specializationDto, resulting in the "no matching editors or conversion strategy found" exception that you've observed. However, if you send the request through postman or curl with (note the dot-notation for the specializationDto object)

curl --location --request POST 'http://localhost:8080/upload' \
--form 'files=@"/path/to/somefile"' \
--form 'files=@"/path/to/somefile"' \
--form 'specializationDto.something="someValue"'

then Spring is able to parse it correctly. Here's my rest-mapping that will log the following as expected:

    @RequestMapping(value = "/upload", method = RequestMethod.POST, 
consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public void upload(@ModelAttribute TeacherDto requestDto) {
System.out.println(requestDto);
}

// logs:
TeacherDto(specializationDto=SpecializationDto(something=someValue), files=[org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@78186ea6, org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@461c9cbc])

I suggest you open a bug on their github page.

EDIT:

After OP opened a github ticket, here's part of the author's feedback:

[...] With spring, you can use @RequestPart spring annotation to describe
the different parts, with the related encoding media type. Note that
there is a limitation with the current swagger-ui implementation as
the encoding attribute is not respected on the request.[...]

They also provided a possible workaround, which looks like this:

@PostMapping(consumes =  MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> saveNewTeacher( @RequestPart(value = "specializationDto") @Parameter(schema =@Schema(type = "string", format = "binary")) final SpecializationDto specializationDto,
@RequestPart(value = "files") final List<MultipartFile> files){
return null;
}

Spring MVC Multipart Request with JSON

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


Related Topics



Leave a reply



Submit