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