OpenAPITools / OpenAPITools/openapi-generator

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

Open
#23,848 3 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Trace AbstractJavaCodegen.addNullableImportForOperation and the JSpecify rendering used by SpringCodegen, then compare the shared parameter partials and API templates listed in the issue. Reproduce the confirmed spring-cloud generation with useJspecify=true and verify the generated Java compiles with the Nullable import; check the other named Spring variants for the same result.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, openapi, spring
Domain
backend-api-design, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.