Skip to content

[jaxrs-spec] Bring useRecords records to pojo feature parity - #7

Closed
Ignacio-Vidal wants to merge 3 commits into
masterfrom
records-full-parity
Closed

[jaxrs-spec] Bring useRecords records to pojo feature parity#7
Ignacio-Vidal wants to merge 3 commits into
masterfrom
records-full-parity

Conversation

@Ignacio-Vidal

Copy link
Copy Markdown
Owner

Fork-internal review PR. Base is pr3-jaxrs-userecords (upstream PR OpenAPITools#24188), so the diff below is only the follow-on change.

Summary

useRecords currently emits records with an essentially empty body. This brings those records to feature parity with pojo.mustache, without widening where records are emitted.

Records stay scoped to the implementations of a generated oneOf interface (useOneOfInterfaces=true) and now additionally require useSealed=true. Everything outside that hierarchy — ordinary pojos, inheritance participants, models with additionalProperties — is left untouched and keeps rendering as a class.

What is emitted where

Model (sample) Kind
CatRequest, DogRequest — implement the oneOf interface record, with full parity
PetRequest — the oneOf container sealed interface
PetBase — standalone pojo class, untouched
inheritance participants / additionalProperties models class

Parity features added to record.mustache

Each is gated by a vendor extension computed in postProcessAllModels, so a record only carries what it needs:

  • defaults (scalar, enum, container) applied in a compact canonical constructor — records have no field initialisers, so defaults were previously dropped silently
  • byte[] properties get content-based equals/hashCode — the implicit record equals compares arrays by reference, which is a correctness bug
  • password properties masked in an explicit toString
  • x-is-jackson-optional-nullable properties render as JsonNullable<T> components defaulting to undefined()
  • property doc annotations (@ApiModelProperty / @Schema / MicroProfile @Schema) on the record components
  • generateBuilders produces a flat builder nested in the record (records can't use the pojo's abstract builder hierarchy), carrying the pojo field defaults as a replacement for setters
  • useRecords + withXml is rejected — JAXB requires mutable JavaBeans

Accessor-style bug fixed

oneof_interface.mustache dropped the get prefix based on useRecords alone. Once records also require useSealed, the combination useRecords=true, useSealed=false would generate an interface declaring itemType() while the implementing classes have getItemType()code that does not compile. The conditional now tracks the same gate, pinned by testUseRecordsWithoutUseSealedKeepsClasses.

Verification

  • 198 jaxrs tests green (155 in JavaJAXRSSpecServerCodegenTest), including 4 new tests.
  • Generated output compiles under JDK 17 in both flag combinations (useRecords+useSealed, and useRecords alone).
  • jaxrs-spec-records sample regenerated and compiles; PetBase correctly reverts record → class. FILES manifest unchanged.
  • Docs rows regenerated in both jaxrs-spec.md and jaxrs-cxf-cdi.md (cxf-cdi extends the same codegen).

Review notes

Adds a useRecords option to the jaxrs-spec generator. When useRecords=true, the
concrete subtypes that implement a generated oneOf interface render as Java
records instead of mutable classes.

- JavaJAXRSSpecServerCodegen: useRecords option, and postProcessAllModels marks
  record-eligible implementors with x-jaxrs-record (no parent, no additional
  properties, implements a generated oneOf interface).
- record.mustache renders the record; model.mustache routes x-jaxrs-record to it.
- oneof_interface.mustache declares the discriminator accessor in record style
  (petType()) under useRecords so records satisfy the interface via their
  canonical accessors; sealed.mustache no longer marks a record final.
- pom.mustache bumps java.version to 17 when useRecords (records need JDK 16+).
- Adds testOneOfRecordImplementationGeneration and a jaxrs-spec-records sample
  (registered in the JDK17 samples workflow).

useRecords is independent of useSealed. The feature stays opt-in; default
jaxrs-spec output is unchanged.
@Ignacio-Vidal
Ignacio-Vidal force-pushed the pr3-jaxrs-userecords branch from d0960ba to be5de89 Compare July 30, 2026 21:40
Records stay scoped to the implementations of a generated oneOf interface
(useOneOfInterfaces=true) and now additionally require useSealed=true.
Models outside that hierarchy - ordinary pojos, inheritance participants
and models with additionalProperties - are left untouched and keep
rendering as classes.

Brings record.mustache to feature parity with pojo.mustache for the
records that are emitted:

- defaults (scalar, enum, container) applied in a compact canonical
  constructor
- x-is-jackson-optional-nullable properties render as JsonNullable
  components defaulting to undefined
- byte[] properties get content-based equals/hashCode overrides (the
  implicit record equals compares arrays by reference)
- password properties are masked in an explicit toString override
- property doc annotations (@ApiModelProperty/@Schema/MicroProfile
  @Schema) emitted on the record components
- generateBuilders produces a flat builder inside the record carrying
  the pojo field defaults, as a fluent replacement for setters
- useRecords + withXml is rejected (JAXB requires mutable JavaBeans)

The oneOf interface only drops the get-prefix from its accessor when
records are actually emitted (useRecords and useSealed), so with
useRecords alone the interface keeps getX() and still matches the classes
implementing it.
@Ignacio-Vidal
Ignacio-Vidal changed the base branch from pr3-jaxrs-userecords to master August 1, 2026 09:38
useRecords is implemented in the shared model/record templates, so it applies
to all five jaxrs-spec libraries, but only the default one had a sample. Add a
records sample per library - helidon, kumuluzee, openliberty, quarkus and
thorntail - and register all five in the JDK17 samples workflow so they are
compiled on every push and pull request.

Two things had to be fixed to make those samples compile:

* Each library overrides pom.mustache and hardcoded Java 1.8 (helidon: 11), so
  a records or sealed sample could not build. Gate the compiler source/target
  on useSealed/useRecords exactly as the base pom.mustache already does.

* record.mustache imported JsonNullable unconditionally. The import is already
  contributed through the imports block for the models that use it, so records
  with a nullable component carried a duplicate import, and records without one
  carried an unused import that fails to compile on the libraries whose pom
  does not ship jackson-databind-nullable.

kumuluzee, openliberty and thorntail additionally set openApiNullable=false in
their sample config: their poms omit jackson-databind-nullable while the pojo
template imports JsonNullable whenever openApiNullable is on, which is a
pre-existing issue unrelated to records (it reproduces with no records flags
set at all).

All six records samples plus jaxrs-spec-sealed build under JDK 17.
@Ignacio-Vidal

Copy link
Copy Markdown
Owner Author

Superseded by a new PR that adds records samples for every jaxrs-spec library and builds them in CI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant