-
-
Notifications
You must be signed in to change notification settings - Fork 534
Description
Springdoc fails to generate proper schema information for @RequestPart parameters with generic types (e.g., Map<String, Any>
) in multipart/form-data endpoints. The parameter disappears entirely from Swagger UI, making it impossible for API consumers to understand the expected input format.
This issue is specific to multipart form data endpoints using @RequestPart with generic types - the same generic types work perfectly fine with @RequestBody in regular JSON endpoints.
Note that SpringBoot has absolutely no problem dealing with the Map<String,Any>
- only springdoc does.
To Reproduce
package springfuckup
import io.swagger.v3.oas.annotations.*
import io.swagger.v3.oas.annotations.media.*
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.web.bind.annotation.*
@SpringBootApplication
@RestController
@Tag(name = "Hello World", description = "Simple Hello World endpoint")
class App {
@Operation(description = "Passing a string works")
@PostMapping("/string-works", consumes = ["multipart/form-data"])
fun string(
@Parameter(
description = "A JSON object for file mapping",
required = true,
)
@RequestPart fileMapping: String,
): String {
return "Received file mapping with length ${fileMapping.length}"
}
@Operation(description = "Passing a map doesn't work")
@PostMapping("/map-works", consumes = ["multipart/form-data"])
fun map(
@Parameter(
description = "A JSON object for file mapping",
required = true,
)
@RequestPart fileMapping: Map<String, Any>,
): String {
return "Received file mapping with ${fileMapping.size} entries"
}
}
fun main(args: Array<String>) {
runApplication<App>(*args)
}
fun main(args: Array<String>) {
runApplication<App>(*args)
}
// build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "3.2.5"
id("io.spring.dependency-management") version "1.1.5"
kotlin("jvm") version "1.9.24"
kotlin("plugin.spring") version "1.9.24"
}
group = "springfuckup"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "21"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
springBoot {
mainClass.set("springfuckup.AppKt")
}
./gradlew bootRun
then open http://localhost:8080/swagger-ui/index.html#/Hello%20World/string
and observe that the /map-works
endpoint doesn't show the required request body.
Expected behavior
Required parameter shows for /map-works
in request body just as it does for /string-works
Screenshots
See how the parameter only shows for primitive "string" @RequestPart
but not for Map<String,Any>

Additional context
Full MRE is available here: https://github.com/corneliusroemer/springdocbug