Skip to content

[BUG] [Java] [spring] Properties that are both required and nullable are marked NotNull #24686

Closed

Description

@mdaul-tgtg

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

When a nullable property is required, the generated class has a non-nullable field and getter.

openapi-generator version

7.24.0

OpenAPI declaration file content or url

This is an example spec that I used to reproduce:

{
  "openapi": "3.1.0",
  "info": {
    "title": "Reproduce",
    "version": "1.0.0"
  },
  "paths": {
    "/thing": {
      "get": {
        "operationId": "getThing",
        "responses": {
          "200": {
            "description": "ok",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Thing"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Thing": {
        "type": "object",
        "required": [
          "name",
          "description"
        ],
        "properties": {
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      }
    }
  }
}
Generation Details

Java 17

JAR=openapi-generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar

"$JAVA_HOME/bin/java" -jar "$JAR" generate \
  -i spec.json \
  -g spring \
  -o out \
  --additional-properties=useJspecify=true,hideGenerationTimestamp=true,openApiNullable=false
Steps to reproduce

Run the generator tool on the spec above with the provided options. Observe that the resulting class does not have the right nullability marks:

public class Thing {

  private String name = null; // field should be @Nullable

  /**
   * Get name
   * @return name
   */
  @NotNull // wrong annotation, should be @Nullable
  @Schema(name = "name", requiredMode = Schema.RequiredMode.REQUIRED)
  @JsonProperty("name")
  public String getName() {
    return name;
  }

  @JsonProperty("name")
  public void setName(String name) { // parameter should be @Nullable
    this.name = name;
  }
Related issues/PRs

#23866
#20827

Activity

  1. jpfinne commented on Aug 12, 2026

    @jpfinne
    Contributor

    @mdaul-tgtg this is not a issue with the anyof in 3.1. I reproduce the issue in 3.0.3:

    ---
    openapi: 3.0.3
    info:
      title: Reproduce
      version: 1.0.0
    paths:
      "/thing":
        get:
          operationId: getThing
          responses:
            '200':
              description: ok
              content:
                application/json:
                  schema:
                    "$ref": "#/components/schemas/Thing"
    components:
      schemas:
        Thing:
          type: object
          required:
            - name
            - description
          properties:
            name:
              type: string
              nullable: true
            description:
              type: string
              nullable: true
    

    I get these results:

    Generator useJspecify openApiNullable setter chain getter
    Spring true false void setName(String name) Thing name(String name) @NotNull String getName()
    Spring false false void setName(String name) Thing name(String name) @NotNull String getName()
    Spring true true void setName(JsonNullable<String> name) Thing name(String name) JsonNullable<String> getName()
    Spring false true void setName(JsonNullable<String> name) Thing name(String name) JsonNullable<String> getName()
    restClient true false void setName(String name) Thing name(String name) String getName()
    restClient false false void setName(@jakarta.annotation.Nullable String name) Thing name(@jakarta.annotation.Nullable String name) @jakarta.annotation.Nullable String getName()

    Missing Spring + useOptional and restclient + openanApiNullable

    It means that:

    • java + jspecify + openapiNullable=false, there is a regression. I'll have a look, I've worked on jspecify quite a lot.
    • Spring, the bug already existed. To be checked if it existed before the introduction of jspecify
  2. changed the title [-][BUG] [Java] Properties that are both required and nullable are marked NotNull[/-] [+][BUG] [Java] [spring] Properties that are both required and nullable are marked NotNull[/+] on Aug 12, 2026
  3. mdaul-tgtg commented on Aug 12, 2026

    @mdaul-tgtg
    Author

    @jpfinne I added spring in the title since it's the one I am interested in, and where the bug has always existed.

    this is not a issue with the anyof in 3.1

    But this is what I used to reproduce. Didn't you get the same results as I did with my spec?

  4. jpfinne commented on Aug 12, 2026

    @jpfinne
    Contributor

    @mdaul-tgtg I think we have the same issue using 3.0.x and 3.1.x. It means that the bug is not in the new normalizer rules.

    Quick solution. use these templates for spring
    beanValidation.mustache

    {{#required}}{{^useJspecify}}{{^isReadOnly}}@NotNull {{/isReadOnly}}{{/useJspecify}}{{/required}}{{#isContainer}}{{^isPrimitiveType}}{{^isEnum}}@Valid {{/isEnum}}{{/isPrimitiveType}}{{/isContainer}}{{^isContainer}}{{^isPrimitiveType}}@Valid {{/isPrimitiveType}}{{/isContainer}}{{^openApiNullable}}{{>beanValidationCore}}{{/openApiNullable}}{{#openApiNullable}}{{^useOptional}}{{>beanValidationCore}}{{/useOptional}}{{/openApiNullable}}{{#useOptional}}{{#openApiNullable}}{{#isContainer}}{{^required}}{{>beanValidationCore}}{{/required}}{{/isContainer}}{{/openApiNullable}}{{#openApiNullable}}{{#required}}{{>beanValidationCore}}{{/required}}{{/openApiNullable}}{{/useOptional}}
    

    meaning that @NotNull is not added for jspecify.

    nullableAnnotation_jspecify.mustache

    {{#useJspecify}}{{#lambda.jSpecifyNullable}}{{#required}}{{#isNullable}}{{^openApiNullable}}@Nullable {{/openApiNullable}}{{/isNullable}}{{/required}}{{^required}}{{^useOptional}}{{#openApiNullable}}{{^isNullable}}@Nullable {{/isNullable}}{{/openApiNullable}}{{^openApiNullable}}@Nullable {{/openApiNullable}}{{/useOptional}}{{/required}}{{/lambda.jSpecifyNullable}}{{/useJspecify}}
    

    meaning that required && isNullable && && !openapiNullable -> @Nullable added

    There was no unit test for this use case, that why I missed it in the implementation of JSpecify.

  5. mdaul-tgtg commented on Aug 12, 2026

    @mdaul-tgtg
    Author

    Thank you! For now, I'm just using additionalModelTypeAnnotations to add @NullUnmarked on the generated classes and ignore the issues until this is fixed.

  6. jpfinne commented on Aug 12, 2026

    @jpfinne
    Contributor

    @mdaul-tgtg Then you get something like the class below. How does it solve your issue? Don't you want @Nullable annotations?

    @NullMarked
    public class Thing {
    
      public String getName() {
        return name;
      }
    
      public void setName(String name) {
        this.name = name;
      }
    }
    
  7. mdaul-tgtg commented on Aug 12, 2026

    @mdaul-tgtg
    Author

    @jpfinne I do! I added NullUnmarked which means unspecified nullness - so it is not treated as non-nullable by the rest of the code. But this is a temporary workaround, in the end, I want the right annotations and nullability handling. Sorry if it was confusing.

  8. itsmib commented on Aug 15, 2026

    @itsmib

    Hi, I'd like to work on this fix. @jpfinne .. are you planning to submit a PR for the template changes you outlined? If not, I'd like to take a shot at it.

  9. jpfinne commented on Aug 15, 2026

    @jpfinne
    Contributor

    @itsmib I have a draft PR #24711 .
    I still need to check what to do with the ‘@NotNull’ annotation. See the regression found by Cubic.
    I think it is needed for Hibernate validation on required and non nullable field.
    For required and nullable I can introduce an annotation like ’@JsonNullableRequired’

  10. jpfinne commented on Aug 15, 2026

    @jpfinne
    Contributor

    @itsmib @mdaul-tgtg can your review #24711?

    And if ok, please approve

    @itsmib using your sample, the generated code becomes:

      private @Nullable String name = null;
      
      @Schema(name = "name", requiredMode = Schema.RequiredMode.REQUIRED)
      @JsonProperty("name")
      public @Nullable String getName() {
        return name;
      }
      public Thing name(@Nullable String name) {
        this.name = name;
        return this;
      }
      @JsonProperty("name")
      public void setName(@Nullable String name) {
        this.name = name;
      }
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions