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