javaspring-bootopenapiopenapi-generator

Issue with OpenAPI Generator not respecting `nullable: false` for array elements in Java Spring code


I'm using the OpenAPI Generator with the Maven plugin to generate Java Spring code. In my OpenAPI spec, I have a Project schema with a skills array. I want non null items in this array.

The problem is that in the generated Java code, the @NotNull annotation for the array items is missing, which allows null values inside the skills array, which is not the expected behavior.

Here’s the relevant section from my api-spec.yml:

components:
  schemas:
    Project:
      type: object
      properties:
        skills:
          type: array
          items:
            type: string
            nullable: false
            minLength: 1
            maxLength: 30

In the generated javacode the skills field does not include the @NotNull annotation for array items:

private List<@Size(min = 1, max = 30)String> skills;

I expected it to generate something as below:

private List<@NotNull @Size(min = 1, max = 30) String> skills;

I am using bean validation with Jakarta EE and it is all configured in maven plugin as below.

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>7.7.0</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/api/api-spec.yml</inputSpec>
                <generatorName>spring</generatorName>
                <output>${project.build.directory}/generated-sources/openapi</output>
                <cleanupOutput>true</cleanupOutput>
                <packageName>com.company.project.api</packageName>
                <generateSupportingFiles>false</generateSupportingFiles>
                <generateModels>true</generateModels>
                <generateModelTests>false</generateModelTests>
                <generateModelDocumentation>false</generateModelDocumentation>
                <modelNamePrefix>Api</modelNamePrefix>
                <modelPackage>com.company.project.api.model</modelPackage>
                <generateApis>true</generateApis>
                <generateApiTests>false</generateApiTests>
                <generateApiDocumentation>false</generateApiDocumentation>
                <apiPackage>com.company.project.api</apiPackage>
                <configOptions>
                    <sourceFolder>/</sourceFolder>
                    <dateLibrary>java8</dateLibrary>
                    <openApiNullable>false</openApiNullable>
                    <useResponseEntity>true</useResponseEntity>
                    <interfaceOnly>true</interfaceOnly>
                    <useSpringBoot3>true</useSpringBoot3>
                    <skipDefaultInterface>true</skipDefaultInterface>
                    <annotationLibrary>none</annotationLibrary>
                    <documentationProvider>none</documentationProvider>
                    <useBeanValidation>true</useBeanValidation>
                    <performBeanValidation>true</performBeanValidation>
                    <useTags>true</useTags>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>

How can I ensure that the generated code enforces the non-null constraint for array items?


Solution

  • One possible solution that you can try right now is to apply a custom annotation on your array field and then write a custom validation based on that that annotation.

    components:
      schemas:
        Project:
          type: object
          properties:
            skills:
              type: array
              x-field-extra-annotation: @bla.bla.CustomAnnotation
              items:
                type: string
                nullable: false
                minLength: 1
                maxLength: 30
    

    CustomAnnotation

    package bla.bla;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import bla.bla.CustomValidator;
    
    @Constraint(validatedBy = CustomValidator.class)
    @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CustomAnnotation {
        String message() default "custom error message";
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};
    }
    

    CustomValidator

    package bla.bla;
    
    import jakarta.validation.ConstraintValidator;
    import jakarta.validation.ConstraintValidatorContext;
    
    public class CustomValidator implements ConstraintValidator<CustomAnnotation, List<String>> {
    
        @Override
        public void initialize(CustomAnnotation constraintAnnotation) {
            // Initialization logic if necessary
        }
    
        @Override
        public boolean isValid(List<String> value, ConstraintValidatorContext context) {
            return true; //or false after performing validation.
        }
    }
    

    :: Following is not an answer but an attempt to find a better solution by making changes in open api generator

    Currently open api generator doesn't annotate generic parameter type as not null or use x-field-extra-annotation. I looked into open api generator and found that relevant code is being generated in org.openapitools.codegen.languages.AbstractJavaCodegen#getStringBeanValidation so locally I added following lines in this method after pattern related code.

    if (!Boolean.TRUE.equals(items.getNullable())) {
                validations = String.join("",validations, "@NotNull ");
            }
    
            Map<String, Object> extensions = items.getExtensions() != null ? items.getExtensions() : Map.of();
            if (extensions.containsKey("x-field-extra-annotation")) {
                validations = String.join("",
                        validations,
                        (String) extensions.get("x-field-extra-annotation"),
                        " "
                        );
            }
    

    Then I tried it with following schema in spec.

    components:
      schemas:
        Project:
          type: object
          properties:
            skills:
              type: array
              items:
                type: string
                nullable: false
                minLength: 1
                maxLength: 30
                x-field-extra-annotation: '@bla.bla.Dummy'
    
    

    It generated following code which seems correct now.

    private List<@NotNull @bla.bla.Dummy @Size(min = 1, max = 30)String> skills;
    

    But this requires a change in open api generator project. I am not an opn api expert (in fact I just explored the generator code for the first time). So this better to be discussed with open api generator team whether this is feasible or not.