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

Time:12-30

I have a DTO that contains other DTOs and a list of multipart files. I am trying to process that DTO but I can't seem to be able to read the requst.

class TeacherDTO {
   private SpecializationDto specializationDto;
   private List<MultipartFile> files;
}

@PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE},
            produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Object> saveNewTeacher(@ModelAttribute @Valid TeacherDTO teacherDto){

//process request

}

When creating an example request from Swagger UI, I get the following exception:

type 'java.lang.String' to required type 'SpecializationDto' for property 'specializationDto': no matching editors or conversion strategy found

If I put @RequestBody instead of @ModelAttribute then I get

Content type 'multipart/form-data;boundary=----WebKitFormBoundaryVEgYwEbpl1bAOjAs;charset=UTF-8' not supported]

Swagger dependencies:

<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-ui</artifactId>
   <version>1.5.2</version>
</dependency>
<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-data-rest</artifactId>
   <version>1.5.2</version>
</dependency>

OpenAPI3.0 config:



@Configuration
public class OpenApi30Config {

  private final String moduleName;
  private final String apiVersion;

  public OpenApi30Config(
      @Value("${spring.application.name}") String moduleName,
      @Value("${api.version}") String apiVersion) {
    this.moduleName = moduleName;
    this.apiVersion = apiVersion;
  }

  @Bean
  public OpenAPI customOpenAPI() {
    final var securitySchemeName = "bearerAuth";
    final var apiTitle = String.format("%s API", StringUtils.capitalize(moduleName));
    return new OpenAPI()
        .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
        .components(
            new Components()
                .addSecuritySchemes(securitySchemeName,
                    new SecurityScheme()
                        .name(securitySchemeName)
                        .type(SecurityScheme.Type.HTTP)
                        .scheme("bearer")
                        .bearerFormat("JWT")
                )
        )
        .info(new Info().title(apiTitle).version(apiVersion));
  }
}

CodePudding user response:

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.

CodePudding user response:

Have you tried to use @RequestMapping instead of @PostMapping ? Something like this:

@RequestMapping(value = "/ex/foos", method = RequestMethod.POST)
public ResponseEntity<Object> saveNewTeacher(@Valid @RequestBody TeacherDTO teacherDTO) throws Exception {
    //process instance
}

class TeacherDTO {
   @Valid
   private SpecializationDto specializationDto;
   private List<MultipartFile> files;
}

//supposing your nested DTO is something like that
class SpecializationDto {
    @NotNull
    @NotEmpty
    String something;
}
  • Related