Skip to content

Non-primitive types for RequestPart don't show when consuming multipart/form-data #3049

@corneliusroemer

Description

@corneliusroemer

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>

Image

Additional context

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions