Skip to content

[BUG][Spring] useJspecify=true can emit @Nullable without importing org.jspecify.annotations.Nullable #23848

Description

@ThalysGomes

Description

When using the spring generator with useJspecify=true, generated API interfaces can contain @Nullable in parameter types/signatures without the required import:

import org.jspecify.annotations.Nullable;

This causes compilation failures in generated sources.

Confirmed affected case

Confirmed with:

  • generator: spring
  • library: spring-cloud
  • options:
    • useJspecify=true
    • openApiNullable=false
    • useSpringBoot3=true

Generated output example

Generated Spring API interfaces can contain method parameters such as:

ResponseEntity<APIProxyDeploymentDetailsEnv> getAPIProxyDeploymentEnv(
    @Parameter(name = "org_name", description = "Organization name.", required = true, in = ParameterIn.PATH)
    @PathVariable("org_name")
    @Nullable String orgName,
    @Parameter(name = "env_name", description = "Environment name.", required = true, in = ParameterIn.PATH)
    @PathVariable("env_name")
    String envName,
    @Parameter(name = "api_name", description = "API proxy name.", required = true, in = ParameterIn.PATH)
    @PathVariable("api_name")
    String apiName
);

but the generated imports do not include:

import org.jspecify.annotations.Nullable;

This causes compilation errors like:

error: cannot find symbol
  symbol:   class Nullable

Upstream source inspection

While investigating the generator source, this appears to be in the shared Spring parameter/import path rather than in any project-specific template customization.

Relevant files in this repository:

  • modules/openapi-generator/src/main/resources/JavaSpring/pathParams.mustache
  • modules/openapi-generator/src/main/resources/JavaSpring/queryParams.mustache
  • modules/openapi-generator/src/main/resources/JavaSpring/headerParams.mustache
  • modules/openapi-generator/src/main/resources/JavaSpring/bodyParams.mustache
  • modules/openapi-generator/src/main/resources/JavaSpring/cookieParams.mustache
  • modules/openapi-generator/src/main/resources/JavaSpring/nullableAnnotation.mustache
  • modules/openapi-generator/src/main/resources/JavaSpring/optionalDataType.mustache
  • modules/openapi-generator/src/main/resources/JavaSpring/api.mustache
  • modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-http-interface/api.mustache
  • modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java
  • modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java

In particular:

  • SpringCodegen sets the mapping:
importMapping.put("Nullable", useJspecify ? "org.jspecify.annotations.Nullable" : "org.springframework.lang.Nullable");
  • AbstractJavaCodegen.applyJspecify() also maps Nullable to org.jspecify.annotations.Nullable.
  • AbstractJavaCodegen.addNullableImportForOperation(...) adds the Nullable import only when a parameter matches notRequiredOrIsNullable().
  • AbstractJavaCodegen.JSpecifyNullableLambda / jSpecifyDatatype are involved in moving @Nullable into the type position.

That means there is at least one Spring generation path where @Nullable reaches the rendered signature but codegenOperation.imports does not receive Nullable.

Scope / likely affected variants

This is confirmed for spring + library=spring-cloud.

From source inspection, the same shared Spring parameter partials are reused by other Spring generator variants, so it is worth validating at least:

  • spring + library=spring-boot
  • spring + library=spring-http-interface
  • delegate/interface generation paths using JavaSpring/api.mustache

I am not claiming all of those are confirmed broken, only that they appear to share the same template path and should be checked.

Expected behavior

Whenever @Nullable is emitted and useJspecify=true, the generated file should also include:

import org.jspecify.annotations.Nullable;

Actual behavior

@Nullable appears in generated Spring API signatures, but the corresponding import is sometimes missing.

Workaround

As a workaround, I post-process generated files and inject the missing import whenever @Nullable is present but the import is missing:

// OAG spring-cloud template emits @Nullable on required object-type parameters but
// doesn't always add the import when useJspecify=true (known generator bug).
// Post-process to ensure the import is present wherever @Nullable is used.
fileTree(layout.buildDirectory.dir("generated-code")).matching {
    include '**/*.java'
}.each { file ->
    def text = file.text
    if (text.contains('@Nullable')
            && !text.contains('import org.jspecify.annotations.Nullable;')) {
        file.text = text.replaceFirst(
            '(package [^;]+;\\s*\\n)',
            '$1import org.jspecify.annotations.Nullable;\n'
        )
    }
}

Version

Observed with:

  • OpenAPI Generator: 7.22.0

Suggested fix

Please review the Spring generator import registration for JSpecify-enabled parameter rendering and ensure that every code path that can emit @Nullable also registers Nullable in the operation/class imports.

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