quarkus
                                
                                 quarkus copied to clipboard
                                
                                    quarkus copied to clipboard
                            
                            
                            
                        MultipartForm is receiving null parameters at the server
Describe the bug
When using resteasy-reactive in Kotlin (I didn't test with Java), when I have an endpoint like this:
import org.jboss.resteasy.reactive.MultipartForm
    @POST
    @Path("/image/{id}")
    @RolesAllowed(MANAGER_ROLE, ADMIN_ROLE)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Operation(summary = "Add images to a brand")
    @APIResponses(
        APIResponse(responseCode = "200", description = "Successful image registration"),
        APIResponse(responseCode = "401", description = "User is not logged in"),
        APIResponse(responseCode = "403", description = "User doesn't have authorization to add images to a brand")
    )
    fun addImage(@PathParam("id") brand: Long, @MultipartForm images: ImageUploadDto): Uni<Response> {
        return identity.deferredIdentity.onItem().transformToUni { id ->
            logger.info("User ${id.principal.name} is adding ${images} image to brand with id $brand")
            return@transformToUni brandService.addImages(brand, images)
        }
    }
With ImageUploadDto being:
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType
import org.eclipse.microprofile.openapi.annotations.media.Schema
import org.jboss.resteasy.reactive.PartType
import org.jboss.resteasy.reactive.RestForm
import java.io.File
import javax.ws.rs.core.MediaType
class ImageUploadDto {
    @RestForm("image")
    @PartType(MediaType.APPLICATION_OCTET_STREAM)
    @Schema(type = SchemaType.STRING, format = "binary")
    var content: String? = null
    @RestForm
    var file: File? = null
}
In swagger, content has a button to allow me to upload a file and there is a file field which is the JSON mapping of the java.io.File class to JSON. I tried with org.jboss.resteasy.reactive.multipart.FileUpload but the behavior was the same as using File (except that FileUpload is an interface so the JSON mapping was empty).
Besides this, when data gets to the server, content is null as well as File (I defined the variables as nullable because I received a NullPointerException otherwise) as it is able to create an ImageUploadDto instance but the properties are null. I followed the example in the documentation (just adapted it to Kotlin) and it doesn't work
Expected behavior
Get the file contents
Actual behavior
Everything is null
How to Reproduce?
Use the example above and try to upload a file
Output of uname -a or ver
Linux zirconium 5.18.16-zen1-1-zen #1 ZEN SMP PREEMPT_DYNAMIC Wed, 03 Aug 2022 11:25:10 +0000 x86_64 GNU/Linux
Output of java -version
openjdk version "18.0.2" 2022-07-19
OpenJDK Runtime Environment (build 18.0.2+9)
OpenJDK 64-Bit Server VM (build 18.0.2+9, mixed mode)
GraalVM version (if different from Java)
No response
Quarkus version or git rev
2.11.2.Final
Build tool (ie. output of mvnw --version or gradlew --version)
------------------------------------------------------------
Gradle 7.5.1
------------------------------------------------------------
Build time:   2022-08-06 13:53:45 UTC
Revision:     <unknown>
Kotlin:       1.6.21
Groovy:       3.0.10
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          18.0.2 (Oracle Corporation 18.0.2+9)
OS:           Linux 5.18.16-zen1-1-zen amd64
Additional information
No response
/cc @evanchooly, @geoand
Can you also attach what the HTTP request looks like (an easy way to do this is from the browser's developer tools)?
The HTTP request looks like this:
POST /brand/image/1 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://localhost:8080/q/swagger-ui/
Content-Type: multipart/form-data; boundary=---------------------------104025827140925644591275308632
Content-Length: 720
Origin: http://localhost:8080
DNT: 1
Connection: keep-alive
Cookie: identity=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo4MDgwIiwic3ViIjoiYWRtaW4iLCJyb2xlcyI6WyJBRE1JTiJdLCJpYXQiOjE2NjAxMTg0MjUsImV4cCI6MTY2MDEyOTIyNSwianRpIjoiOTZlMWQxZTgtNjU1NC00MGU2LWEzZDItZmM3OGVjNDAwMWRkIn0.U9yGfjNwlQwlCQp66DFFgefpA-XUua049ZfM14YTkStkb2sBe33mgrtJURH1a_8hGYdlphjSCTqw4DHBpmWDih_3nYQnv5WtwSh2uZN-phdoyiO1Cq4hD_X86cadKBAMneUapHdV5ZYBhMi4DhJJmNtwKHrhZkrMssr9DAxVON2-gSsBFD5ieyj0kDS30MNxJIpJ_vIGebA3HZam9SAdZO5RLV7zwi8NfcH-NTVFsjkgBnA2wNCxwzC8EqOqXE13EC69gi8teJf_ir2I4Qr5JdpwyrmyiDBxRflxiULmaFC8YMbj4Arcwgr6ZjIq8StsgR03PN4Y0kUGh9HHdlB0FeM8YYskMEuEkjYLEmxO69pbQFeDMAi19d4EyX5HxR7tMpSMSmec02q1hy8JN3lxEesL_CrpXmhyU1Cj0LaNyPavSh75-hobrXz5Sm_dt2vLuHE--OKwUJwOQtldFwhTZQmX9ad7GI_bESkdofEggIbV0WXP1reF3zWqFTEWkabcPmvjewKvCxkNLd8x-uKW60DF0lqk_suzeUZm8aFhhzsAadNakipLCqp20z_G_qjJ6CihKACMC1ENJ7IcNz_bPN8rAprKLLPBr0QptoH8wht232D5eRZscF8tflhLIZrFtMO4iTZNYMsJ0e1ChEwwEwJ97uSgxSFbkcwqIy_JcXf1suuW_X96yM6iH0nRLMuv6Sv87K2j9g5-W0vH3BBXddHcCUsn9VSl9rkxyQEyS5uDE1qjaumcTbznn1uHAm5sTSne_39APm2vCPPYdzmBda8mdnLvyWQrZAaUGYUcXaIo-WvMNwMlX1FIaXvYkgAXlbOTQtBqNv0INB0sg8zmjocEc2uM_UwUoagaIfgtXLX7DsokM9hlM3gs0Nma53PxXmaKKYtomj1pJQDvXtjBwjfQa3QnQGJ2j8wRjh6mWNSOiYsdPk5NywrYCSceWyOPNetQ01Uze8oUOH2xv1W1tYR9s9Fz7Aar3XRpjhE1DtyOhB85hn4zAQOdMerZH81r2L1WtsGVK5rS-PU-67yf69lHFyuWg6K4DimiJDQO4h5ckktvRURtd60zyfUQPzmFTkn1ay5Edt9tNPIMBBJ0HiMZQKRvunBXnIk1U2hLLqItJjNy9xz0tHNwABNbsBEG-0qIgjcjmVgSk3NVkuIBCSARjb-9cmTQaqJtjoX5b_gSyV28W1sxsz07dAzKAuFqU4rYY0dKGAglnZk3rRsHPvAqXwsxxNZ_xKKMIveTXh3qM6e0k1kWazkjxvJQGeHH0J9Ug4rYElB2p4EZcd-AQeI-HAYmdw3mfO_ADO3r-4FaVS9JVkeIeJa_yR_Gp1dhHO9dFSYsVGAnZ6LttSFoig
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Pragma: no-cache
Cache-Control: no-cache
-----------------------------104025827140925644591275308632
Content-Disposition: form-data; name="image"; filename="tmp"
Content-Type: application/octet-stream
123abcd
-----------------------------104025827140925644591275308632
Content-Disposition: form-data; name="file"
{
  "path": "string",
  "invalid": true,
  "name": "string",
  "parent": "string",
  "parentFile": "string",
  "absolute": true,
  "absolutePath": "string",
  "absoluteFile": "string",
  "canonicalPath": "string",
  "canonicalFile": "string",
  "directory": true,
  "file": true,
  "hidden": true,
  "totalSpace": 0,
  "freeSpace": 0,
  "usableSpace": 0
}
-----------------------------104025827140925644591275308632--
Content-Disposition: form-data; name="file" contains no filename, so using FileUpload or File will not yield any results.
The file parameter is the one on ImageUploadDto, it should not appear in swagger at all. It doesn't have a filename because it is showing as a parameter and it shouldn't
So this sounds more like a SwaggerUI issue - which makes sense since I don't think we have any support for RESTEasy Reactive multipart handling in OpenAPI
But sending through curl yields the same error, I tried sending a local file with curl and also got null in both parameters
If you do not specify a value for @RestForm the name of the section is assumed to be the field name, so please make sure you are using the proper values.
Yes, but that is generated in Swagger, the problem is that the file contents do not get to the backend
Any news on this issue?
Not at the moment.
We have a large refactoring of the multipart support we want to get in before making any other changes to it.
Are there any alternatives I can use to upload images then?
See my comment above.
Is there an estimated date for when the refactoring will be done?
The refactoring has been done and merged in main.
@Sgitario would you like to take another look at this issue?
I guess you mean https://github.com/quarkusio/quarkus/pull/27526 which is already part of 2.14.0.CR1. @DMaxter can you try the Quarkus version 2.14.0.CR1 and see if it works now?
The example in the updated docs is generating the following on Swagger:

With this example, OpenAPI is able to generate an upload button and the content of the file can be read inside my application
    @POST
    @Path("/upload")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    fun upload(@PartType(MediaType.APPLICATION_OCTET_STREAM) @Schema(type = SchemaType.STRING, format = "binary") @RestForm description: FileUpload,
               @RestForm("image") file: File?
    ): Uni<Response> {
        println(description.uploadedFile().toFile().readText())
        println("DONE")
        return Uni.createFrom().item(Response.ok().build())
    }
Thank you