Skip to content
Advertisement

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

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

Advertisement

Answer

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"rnrn{rn  "something": "someValue"rn}


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