Skip to content

[BUG][JAVA][Spring] useJspecify=true incorrectly annotates first @PathVariable with @Nullable and omits the import #23653

@raichuchuchu

Description

@raichuchuchu

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 useJspecify=true is set, OAG 7.22.0 unconditionally annotates the first @PathVariable in every generated Spring API interface with @Nullable. This is wrong for two reasons:

  1. Path parameters must not be nullable. The OpenAPI Specification mandates that path parameters have required: true. There is no concept of an optional path segment. The correct annotation — if any — would be @NonNull, not @Nullable.

  2. The import is not generated. The API interface template does not emit import org.jspecify.annotations.Nullable; for this annotation. The build fails with cannot find symbol @Nullable unless the import is coincidentally pulled in by another element in the same file (e.g., an optional @RequestBody(required = false) that is correctly annotated @Nullable).

This makes the bug silently present in projects where the import happens to exist, and a hard compile error in projects where it does not — with both projects using identical generator configuration.

The same issue does not occur with useJspecify=false.


openapi-generator version

7.22.0


OpenAPI declaration file content or url

Minimal spec to reproduce. The first operation must have a path parameter and no request body:

openapi: 3.0.3
info:
  title: Minimal Repro
  version: 1.0.0
paths:
  /items/{itemId}:
    post:
      operationId: createItem
      tags:
        - Items
      parameters:
        - name: itemId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '201':
          description: Created

Generation Details
// build.gradle (Gradle plugin)
openApiGenerate {
    generatorName = 'spring'
    configOptions = [
        useSpringBoot4: 'true',
        useJackson3   : 'true',
    ]
    additionalProperties = [
        useJspecify  : 'true',
        useJakartaEe : 'true',
        interfaceOnly: 'true',
    ]
}

Equivalent CLI config:

generatorName: spring
additionalProperties:
  useJspecify: "true"
  useJakartaEe: "true"
  interfaceOnly: "true"
configOptions:
  useSpringBoot4: "true"
  useJackson3: "true"

Steps to reproduce
  1. Create a spec with the YAML above.
  2. Configure the Spring generator with useJspecify=true as shown.
  3. Run generation.
  4. Observe the first generated method in the API interface — the path parameter is annotated @Nullable and the import org.jspecify.annotations.Nullable is absent.
  5. Build the project → error: cannot find symbol @Nullable.

Related issues/PRs

Suggest a fix

The API interface template (api.mustache or equivalent) should:

  1. Not emit @Nullable on path parameters — they are always required by the OpenAPI Specification.
  2. Emit @NonNull instead, or no nullability annotation at all, which is the correct semantic.
  3. If @Nullable must be emitted in any case, ensure import org.jspecify.annotations.Nullable; is always included in the generated file when useJspecify=true, regardless of whether other nullable references are present.

Observed vs. expected output

useJspecify=false — correct:

// No nullability import
default ResponseEntity<Void> createItem(
    @PathVariable("itemId")
    UUID itemId) { ... }

useJspecify=true, no @RequestBody in file — compile error:

// import org.jspecify.annotations.Nullable;  ← NOT generated → compile error

default ResponseEntity<Void> createItem(
    @PathVariable("itemId")
    @Nullable              // ← incorrectly generated
    UUID itemId) { ... }

useJspecify=true, file contains an optional @RequestBody — bug silently present:

import org.jspecify.annotations.Nullable;  // ← pulled in by @RequestBody(required = false), not by the path param

default ResponseEntity<Void> createItem(
    @PathVariable("itemId")
    @Nullable              // ← still incorrectly generated, but import exists → compiles
    UUID itemId,
    @Valid @RequestBody(required = false)
    @Nullable MyRequest body) { ... }  // ← @Nullable here is correct; body is optional

The @Nullable on @RequestBody(required = false) is legitimate. It is this correct usage that incidentally provides the import, masking the path parameter bug. This is why two projects with identical generator config can have different build outcomes.

Expected output (if annotation is desired):

import org.jspecify.annotations.NonNull;

default ResponseEntity<Void> createItem(
    @PathVariable("itemId")
    @NonNull               // ← path params are always required
    UUID itemId) { ... }

Workaround

Disable useJspecify until the template is fixed:

additionalProperties = [
    // useJspecify: 'true',  // disabled: OAG 7.22.0 bug — first @PathVariable incorrectly gets @Nullable without import
]

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