Skip to content

Conversation

jreynard-code
Copy link

In kotlin-spring generator, for endpoints dealing with MultipartFile, we found out that it was not possible to declare Array<MultipartFile> property as "nullable".

Our use case consist on having a POST endpoint (multipart/form-data) that deals with 2 parts:

  • an array of file
  • a json description

Basically, something like this:

paths:
  /nullable-multipartfile-array:
    post:
      tags:
        - multipartfile
      summary: simple nullable multipartfile
      operationId: testNullableMultipartfile
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                files:
                  type: array
                  items:
                    type: string
                    description: File to upload
                    format: binary
                jsonPayload:
                  type: object
                  description: simple json payload
                  properties:
                    name:
                      type: string
              required:
                - jsonPayload

As you can see, files is not marked as required.
What we expected from the kotlin-spring generator, for the associated controller was:

@RequestPart("files", required = false) files: Array<org.springframework.web.multipart.MultipartFile>?

but it generated

@RequestPart("files", required = false) files: Array<org.springframework.web.multipart.MultipartFile>

However, the ? was correctly added to File property

In this PR, we have adapted the optionalDataType.mustache template in order to put the ? even for Array.
We've also added some tests in file org.openapitools.codegen.kotlin.spring.KotlinSpringServerCodegenTest

However, we're a bit confused by nullable and required usage along the whole generator.
In this particular use case, we could imagine that:

  • files can be set as required => @RequestPart("files", required = false)
  • files can be nullable => files: Array<org.springframework.web.multipart.MultipartFile>?

Do you have some guidelines about when and where use these 2 keywords ?

Thanks for your handsome work !

@karismann (2019/03) @Zomzog (2019/04) @andrewemery (2019/10) @4brunu (2019/11) @yutaka0m (2020/03) @stefankoppier (2022/06) @e5l (2024/10)

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • File the PR against the correct branch: master (upcoming 7.x.0 minor release - breaking changes with fallbacks), 8.0.x (breaking changes without fallbacks)
  • If your PR solves a reported issue, reference it using GitHub's linking syntax (e.g., having "fixes #123" present in the PR description)
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

@jreynard-code jreynard-code changed the title [FEAT] Support nullable org.springframework.web.multipart.MultipartFile in Kotlin-Spring generator [FEAT] Support nullable Array<org.springframework.web.multipart.MultipartFile> in Kotlin-Spring generator Sep 18, 2025
@wing328
Copy link
Member

wing328 commented Sep 23, 2025

@RequestPart("files", required = false) files: Array<org.springframework.web.multipart.MultipartFile>?

not an expert in kotlin, given that required is already set to false, what's the difference between including and not including ? at the end as required=false means the parameter is optional?

@jreynard-code
Copy link
Author

jreynard-code commented Sep 26, 2025

Hi @wing328,

First of all, thanks for your reply and excuse me for my response delay :s

Kotlin has a Null Safety feature in core (https://kotlinlang.org/docs/null-safety.html)
It means that except if you specify explicitly the fact that a value can be null, there is no possible null value.

Let's take the previous example and let me illustrate with curl commands.

paths:
  /nullable-multipartfile-array:
    post:
      tags:
        - multipartfile
      summary: simple nullable multipartfile
      operationId: testNullableMultipartfile
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                files:
                  type: array
                  items:
                    type: string
                    description: File to upload
                    format: binary
                jsonPayload:
                  type: object
                  description: simple json payload
                  properties:
                    name:
                      type: string
              required:
                - jsonPayload

Regarding the example, I'm expecting that the files property will be marked as required=false and this is OK.
However, it's only meaning that a valid request should contain (or not) a part named files.
It does not add a notion of null here, just a check if the param is sent or not.

For me, a fix should be to take the fact that the files property is marked as nullable or not (which is not the case in template)

To illustrate:

Example 1:

          multipart/form-data:
            schema:
              type: object
              properties:
                files:
                  type: array
                  items:
                    type: string
                    description: File to upload
                    format: binary
                jsonPayload:
                  type: object
                  description: simple json payload
                  properties:
                    name:
                      type: string
              required:
                - jsonPayload

should lead to

@RequestPart("files", required = false) files: Array<org.springframework.web.multipart.MultipartFile>

which means :

  • you don't need to send a files part (required=false)
  • if you do, it should have value (nullable=false by default)

And

Example 2:

          multipart/form-data:
            schema:
              type: object
              properties:
                files:
                  type: array
                  items:
                    type: string
                    description: File to upload
                    format: binary
                    nullable: true
                jsonPayload:
                  type: object
                  description: simple json payload
                  properties:
                    name:
                      type: string
              required:
                - jsonPayload

should lead to

@RequestPart("files", required = false) files: Array<org.springframework.web.multipart.MultipartFile>?

which means :

  • you don't need to send a files part (required=false)
  • If you do, files can have a null value (nullable: true specified)

If I use a cURL command:

curl -X 'POST' \
  'https://localhost:8080/nullable-multipartfile-array' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'files=' \
  -F 'jsonPayload={"name":"empty parts"}'

in example 1, this leads to a "java.lang.NullPointerException: Parameter specified as non-null is null "
in example 2, this is ok.

And that's why I'm a bit confused by the 2 keywords nullable and required mainly because I don't know if it's normal or not (globally in this generator).

When I looked at the optionalDataType.mustache file, I saw that nullable was not used at all and that the ? was conditionned by {{^required}}?{{/required}} instead.

However, the aim of my fix is to correct the fact that a Array<> part can also be nullable (with a ?) but it does not correct the fact that nullable should be used instead of required here.

I don't know if it helps but tell me ;)

Regards,

Jérémy

@wing328
Copy link
Member

wing328 commented Sep 29, 2025

ok. not sure if it helps, the following mustache template files for kotlin-client contain isNullable:

modules/openapi-generator/src/main/resources/kotlin-client/model_room.mustache
modules/openapi-generator/src/main/resources/kotlin-client/data_class_req_var.mustache
modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-okhttp/api.mustache:

would these help understand how nullable is used in templates to generate Kotlin code?

@jreynard-code
Copy link
Author

jreynard-code commented Sep 29, 2025

I think it helps :)

In our case, I'll change the PR by replacing {{^required}}?{{/required}} to {{#isNullable}}?{{/isNullable}} in optionalDataType.mustache.
I do really prefer this because:

  • this is the same mechanism as in data_class_req_var.mustache file (consistency)
  • we can have the granularity that I listed above (improvement)

Thanks ;)

@wing328
Copy link
Member

wing328 commented Oct 4, 2025

https://github.com/OpenAPITools/openapi-generator/actions/runs/18093579898/job/51946861968?pr=21994

please update the samples when you've time

@@ -1 +1 @@
{{^isFile}}{{{dataType}}}{{^required}}{{^defaultValue}}?{{/defaultValue}}{{/required}}{{/isFile}}{{#isFile}}{{#isArray}}Array<{{/isArray}}org.springframework.web.multipart.MultipartFile{{#isArray}}>{{/isArray}}{{^isArray}}{{^required}}?{{/required}}{{/isArray}}{{/isFile}} No newline at end of file
{{^isFile}}{{{dataType}}}{{^required}}{{^defaultValue}}?{{/defaultValue}}{{/required}}{{/isFile}}{{#isFile}}{{#isArray}}Array<{{/isArray}}org.springframework.web.multipart.MultipartFile{{#isArray}}>{{/isArray}}{{#isNullable}}?{{/isNullable}}{{/isFile}} No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about optional schema in which isNullable is set to false? should it also generate the ? at the end to make it nullable in kotlin?

Copy link
Author

@jreynard-code jreynard-code Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean exactly?

Something equivalent to Optional<T> in Java?

… Kotlin Spring generator

- nullable is only supported for MultipartFile. However, Array<MultipartFile> could be also nullable
… Kotlin Spring generator

Update kotlin-spring-additionalproperties samples
@jreynard-code jreynard-code force-pushed the make_multipartfile_nullable_when_non_required branch from 17420ec to 4511762 Compare October 6, 2025 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants