Skip to content

[jaxrs-spec] useRecords pojo parity + records samples for every library in CI - #24562

Draft
Ignacio-Vidal wants to merge 3 commits into
OpenAPITools:masterfrom
Ignacio-Vidal:records-full-parity
Draft

[jaxrs-spec] useRecords pojo parity + records samples for every library in CI#24562
Ignacio-Vidal wants to merge 3 commits into
OpenAPITools:masterfrom
Ignacio-Vidal:records-full-parity

Conversation

@Ignacio-Vidal

@Ignacio-Vidal Ignacio-Vidal commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Draft — stacked on #24188.
GitHub cannot target a fork branch as a PR base, so this targets master and
its diff therefore also contains #24188's commit ([jaxrs-spec] Support useRecords). Review only the two commits on top of it:

  • [jaxrs-spec] Bring useRecords records to pojo feature parity
  • [jaxrs-spec] Add records samples for every library and build them in CI

Once #24188 merges, this will be rebased onto master and the diff will
reduce to those two commits. Marked ready for review at that point.

1. Bring useRecords records to pojo feature parity

Records emitted for oneOf-interface implementations were missing behaviour the
pojo template already had. Each feature is gated on a vendor extension computed
in postProcessAllModels, so records without the relevant properties are
unaffected:

extension what it adds
x-jaxrs-record-has-defaults compact canonical constructor applying defaults on null (scalar, enum, container, and JsonNullableof(default) / undefined())
x-jaxrs-record-has-byte-array explicit equals/hashCode using Arrays.*, since the implicit record versions compare arrays by reference
x-jaxrs-record-has-password explicit toString masking format: password values

Also: doc annotations on record components, JsonNullable<T> component types,
and generateBuilders support via a flat static nested builder (records cannot
use the pojo's abstract builder hierarchy).

useRecords + withXml now throws IllegalArgumentException rather than
generating something that cannot work.

2. Samples for every jaxrs-spec library, compiled in CI

useRecords lives in the shared model/record templates, so it applies to all
five libraries — but only the default one had a sample. This adds one per
library (helidon, kumuluzee, openliberty, quarkus, thorntail) and registers all
five in the JDK17 samples workflow, so they are compiled on every push and PR.

Two fixes were needed to make them compile:

  • Library poms hardcoded Java 1.8 (helidon: 11). Every library overrides
    pom.mustache, so none inherited the base template's useSealed/useRecords
    → 17 gating. Applied the same gating to all five.

  • record.mustache imported JsonNullable unconditionally. The import is
    already contributed through the imports block for 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 libraries
    whose pom omits jackson-databind-nullable.

kumuluzee, openliberty and thorntail set openApiNullable=false in their sample
config. Their poms omit jackson-databind-nullable while the pojo template
imports JsonNullable whenever openApiNullable is on. That is pre-existing and
unrelated to records — it reproduces with no records flags set at all — so it is
worked around here rather than fixed in this PR.

Verification

  • 158 JavaJAXRSSpecServerCodegenTest, 730 Java codegen tests — all green
  • All six records samples plus jaxrs-spec-sealed build under JDK 17
  • Docs regenerate with no diff

Two unrelated pre-existing bugs found while testing this are split into their
own PRs rather than bundled here:

  • microprofile client emitting equalsNullable calls whose helpers are never defined
  • jaxrs-spec pojo builder declaring the raw type for a JsonNullable property

Summary by cubic

Brings useRecords in jaxrs-spec to full parity with POJOs for oneOf interface implementations and adds record samples for every library, compiled in CI. Also gates all jaxrs-spec poms to JDK 17 when useRecords/useSealed are enabled and documents the new option.

  • New Features
    • Records are emitted only for oneOf implementors when useOneOfInterfaces=true, useSealed=true, and useRecords=true (JDK 17). All other models remain classes.
    • POJO parity for records: constructor-applied defaults (scalars, enums, containers), JsonNullable<T> components default to undefined(), content-based equals/hashCode for byte[], masked toString for password fields, doc annotations on components, and a flat generateBuilders builder.
    • OneOf interface uses record-style accessor for the discriminator only when records are emitted; otherwise getters remain unchanged.
    • useRecords cannot be combined with withXml (throws IllegalArgumentException).
    • Samples: added record samples for default, helidon, kumuluzee, openliberty, quarkus, and thorntail, and included them in the JDK 17 samples workflow. Fixed duplicate JsonNullable import in record.mustache. Library pom.mustache files now set source/target to 17 when useRecords or useSealed; docs updated to include useRecords.

Written for commit 20a862f. Summary will update on new commits.

Review in cubic

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.
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.
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.
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