Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
bde43b4
feat: add optional getters for nullable fields
jorgerod Jun 16, 2026
0cb169d
chore: solved conflicts
jorgerod Jun 16, 2026
16d08a3
fix: update generated annotation version in EnumConverterConfiguration
jorgerod Jun 16, 2026
8a9121b
feat: add optional getters for nullable fields
jorgerod Jun 16, 2026
5e85ed2
style: fix indentation in nullable getters
jorgerod Jun 16, 2026
7a25329
feat: add configuration files for optional getters in Java clients
jorgerod Jun 16, 2026
f1c6dde
chore: initialize project structure and add configuration files
jorgerod Jun 16, 2026
ecbb9cb
feat: propagate discriminator property to subtype models for optional…
jorgerod Jun 17, 2026
e32abbb
feat: update optional getters description for non-required fields
jorgerod Jun 17, 2026
eb831e9
Merge branch 'master' into feature/GH-24002-optional
jorgerod Jul 21, 2026
5e30ce3
Merge branch 'master' into feature/GH-24002-optional
jorgerod Jul 22, 2026
37ccc89
feat: update optional getters description for non-required fields
jorgerod Jul 22, 2026
ddc8cfe
Merge branch 'master' into feature/GH-24002-optional
jorgerod Jul 23, 2026
c524f59
chore: solved conflicts
jorgerod Sep 1, 2026
6d82076
refactor: update descriptions and remove deprecated options in config…
jorgerod Sep 1, 2026
e8f55a6
Merge branch 'master' into feature/GH-24002-optional
jorgerod Sep 2, 2026
fbd92e3
chore: update version to 7.26.0-SNAPSHOT and bump dependencies
jorgerod Sep 2, 2026
bb6082b
docs: add API documentation for FileApi and RequiredAndNullableApi
jorgerod Sep 2, 2026
4e1abb8
docs: add new PHP keywords to documentation
jorgerod Sep 2, 2026
0d2f1de
Merge branch 'master' into feature/GH-24002-optional
jorgerod Sep 2, 2026
c1832f7
Merge branch 'master' into feature/GH-24002-optional
jorgerod Sep 3, 2026
cc5a645
refactor: remove insecure TLS certificate validation method from ApiC…
jorgerod Sep 3, 2026
dcd811b
Merge branch 'master' into feature/GH-24002-optional
jorgerod Sep 3, 2026
a2c8be7
Merge branch 'master' into feature/GH-24002-optional
jorgerod Sep 3, 2026
bb7840f
Merge branch 'feature/GH-24002-optional' of github.com:InditexTech/fo…
jorgerod Sep 3, 2026
4784547
docs: update elixir documentation for library option
jorgerod Sep 3, 2026
46c57cf
Merge branch 'master' into feature/GH-24002-optional
jorgerod Sep 24, 2026
2f91fcc
refactor: add method to disable X.509 certificate validation in TLS c…
jorgerod Sep 24, 2026
d4319f3
refactor: add method to disable X.509 certificate validation in ApiCl…
jorgerod Sep 24, 2026
9926fb9
chore: fix format
jorgerod Sep 24, 2026
a16c7fb
Update samples/client/petstore/java/resttemplate-springBoot4-jackson3…
jorgerod Sep 24, 2026
65983d9
fix: address cubic-dev-ai review feedback
jorgerod Sep 25, 2026
61d2810
fix: expose optionalGettersForNullableFieldsOnly only in supporting g…
jorgerod Sep 25, 2026
6e4560f
fix: generate Java 17/21 JDKs in .travis.yml for Spring Boot 4 samples
jorgerod Sep 25, 2026
1717b5c
chore: remove unused test files from repository
jorgerod Sep 25, 2026
9b40745
fix: complete Optional getter support for Jackson 2 and custom mappers
jorgerod Sep 25, 2026
b79d526
test: add unit tests for FileApi, FooApi, RequiredAndNullableApi, and…
jorgerod Sep 25, 2026
24c34f1
refactor: restrict optionalGettersForNullableFieldsOnly to jackson 3
jorgerod Sep 25, 2026
04b08b5
fix: require jackson 3 for optional getters for nullable fields
jorgerod Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
generatorName: java
outputDir: samples/client/petstore/java/restclient-springBoot4-jackson3-jspecify-optional-getters
library: restclient
inputSpec: modules/openapi-generator/src/test/resources/3_0/java/jspecify.yaml
templateDir: modules/openapi-generator/src/main/resources/Java
validateSpec: false
additionalProperties:
artifactId: petstore-restclient-optional-getters
hideGenerationTimestamp: "true"
containerDefaultToNull: "true"
useSpringBoot4: true
useJackson3: true
openApiNullable: false
useJspecify: true
optionalGettersForNullableFieldsOnly: "true"
typeMappings:
OffsetDateTime: java.time.Instant
BigDecimal: java.math.BigDecimal
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
generatorName: java
outputDir: samples/client/petstore/java/resttemplate-springBoot4-jackson3-jspecify-optional-getters
library: resttemplate
inputSpec: modules/openapi-generator/src/test/resources/3_0/java/jspecify.yaml
templateDir: modules/openapi-generator/src/main/resources/Java
validateSpec: false
additionalProperties:
artifactId: petstore-resttemplate-optional-getters
hideGenerationTimestamp: "true"
containerDefaultToNull: "true"
useJakartaEe: true
useSpringBoot4: true
useJackson3: true
openApiNullable: false
useJspecify: true
optionalGettersForNullableFieldsOnly: "true"
typeMappings:
OffsetDateTime: java.time.Instant
BigDecimal: java.math.BigDecimal
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
generatorName: java
outputDir: samples/client/petstore/java/webclient-springBoot4-jackson3-jspecify-optional-getters
library: webclient
inputSpec: modules/openapi-generator/src/test/resources/3_0/java/jspecify.yaml
templateDir: modules/openapi-generator/src/main/resources/Java
validateSpec: false
additionalProperties:
artifactId: petstore-webclient-optional-getters
hideGenerationTimestamp: "true"
containerDefaultToNull: "true"
useSpringBoot4: true
useJackson3: true
openApiNullable: false
useJspecify: true
optionalGettersForNullableFieldsOnly: "true"
typeMappings:
OffsetDateTime: java.time.Instant
BigDecimal: java.math.BigDecimal
22 changes: 22 additions & 0 deletions bin/configs/spring-boot-4-jspecify-optional-getters.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
generatorName: spring
library: spring-boot
outputDir: samples/openapi3/server/petstore/springboot-4-jspecify-optional-getters
inputSpec: modules/openapi-generator/src/test/resources/3_0/java/jspecify.yaml
templateDir: modules/openapi-generator/src/main/resources/JavaSpring
validateSpec: false
additionalProperties:
groupId: org.openapitools.openapi3
documentationProvider: springdoc
interfaceOnly: true
artifactId: springboot-optional-getters
snapshotVersion: "true"
useSpringBoot4: true
useJackson3: true
useBeanValidation: true
withXml: true
hideGenerationTimestamp: "true"
generateConstructorWithAllArgs: true
generateBuilders: true
openApiNullable: false
useJspecify: true
optionalGettersForNullableFieldsOnly: "true"
1 change: 1 addition & 0 deletions docs/generators/java.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|microprofileRestClientVersion|Version of MicroProfile Rest Client API.| |null|
|modelPackage|package for generated models| |org.openapitools.client.model|
|openApiNullable|Enable OpenAPI Jackson Nullable library. Not supported by `microprofile` library.| |true|
|optionalGettersForNullableFieldsOnly|Make getters of non-required fields return Optional<T> while keeping the field and setter as the raw type. Requires jackson 3 (useJackson3, which requires useSpringBoot4). Supported libraries: restclient, resttemplate, webclient (java generator) and spring (spring generator). Opt-in, disabled by default.| |false|
|parcelableModel|Whether to generate models for Android that implement Parcelable with the okhttp-gson library.| |false|
|parentArtifactId|parent artifactId in generated pom N.B. parentGroupId, parentArtifactId and parentVersion must all be specified for any of them to take effect| |null|
|parentGroupId|parent groupId in generated pom N.B. parentGroupId, parentArtifactId and parentVersion must all be specified for any of them to take effect| |null|
Expand Down
1 change: 1 addition & 0 deletions docs/generators/spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|modelPackage|package for generated models| |org.openapitools.model|
|openApiNullable|Enable OpenAPI Jackson Nullable library. Not supported by `microprofile` library.| |true|
|optionalAcceptNullable|Use `ofNullable` instead of just `of` to accept null values when using Optional.| |true|
|optionalGettersForNullableFieldsOnly|Make getters of non-required fields return Optional<T> while keeping the field and setter as the raw type. Requires jackson 3 (useJackson3, which requires useSpringBoot4). Supported libraries: restclient, resttemplate, webclient (java generator) and spring (spring generator). Opt-in, disabled by default.| |false|
|optionalNonNullPropertyJsonInclude|The Jackson @JsonInclude policy emitted for optional, non-nullable model properties when generateJsonIncludeAnnotations is true. NONE emits no annotation, deferring fully to the global ObjectMapper inclusion policy.|<dl><dt>**NON_NULL**</dt><dd>Omit the property when its value is null (default, spec-safe for non-nullable fields).</dd><dt>**NON_EMPTY**</dt><dd>Omit the property when its value is null or considered empty.</dd><dt>**NON_DEFAULT**</dt><dd>Omit the property when its value equals the default.</dd><dt>**NONE**</dt><dd>Emit no @JsonInclude annotation; defer to the global ObjectMapper.</dd></dl>|NON_NULL|
|optionalNonNullPropertyJsonSetterNulls|The Jackson @JsonSetter(nulls = ...) mode emitted for optional, non-nullable model properties when generateJsonSetterNullsAnnotations is true. SKIP ignores an explicit JSON null (keeping the field's default), FAIL rejects it. When left unset the mode is derived from openApiNullable (true -&gt; FAIL where supported, false -&gt; SKIP), preserving 7.24.x behavior. A per-property override set via the `x-jackson-json-setter-nulls` vendor extension always wins.|<dl><dt>**SKIP**</dt><dd>Emit @JsonSetter(nulls = Nulls.SKIP): silently ignore an explicit JSON null, keeping the field's default.</dd><dt>**FAIL**</dt><dd>Emit @JsonSetter(nulls = Nulls.FAIL): reject an explicit JSON null.</dd></dl>|null|
|parentArtifactId|parent artifactId in generated pom N.B. parentGroupId, parentArtifactId and parentVersion must all be specified for any of them to take effect| |null|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code
public static final String DEFAULT_TEST_FOLDER = "${project.build.directory}/generated-test-sources/openapi";
public static final String GENERATE_CONSTRUCTOR_WITH_ALL_ARGS = "generateConstructorWithAllArgs";
public static final String GENERATE_BUILDERS = "generateBuilders";
public static final String OPTIONAL_GETTERS_FOR_NULLABLE_FIELDS_ONLY = "optionalGettersForNullableFieldsOnly";

@Getter @Setter
protected String dateLibrary = "java8";
Expand Down Expand Up @@ -181,6 +182,20 @@ protected enum ENUM_PROPERTY_NAMING_TYPE {MACRO_CASE, legacy, original}
@Getter @Setter
protected String booleanGetterPrefix = "get";
@Setter protected boolean ignoreAnyOfInEnum = false;
@Getter @Setter
protected boolean optionalGettersForNullableFieldsOnly = false;
/**
* Whether this generator implements the {@code optionalGettersForNullableFieldsOnly}
* option (explicit Optional&lt;T&gt; getters in the model templates). Only client libraries
* restclient/resttemplate/webclient and the Spring generator honor it, so generators
* whose model templates do not implement it override this method returning false,
* which removes the option from cliOptions and the generated documentation.
*
* @return true when the generator's model templates implement the option
*/
protected boolean supportsOptionalGettersForNullableFieldsOnly() {
return false;
}
@Setter protected String parentGroupId = "";
@Setter protected String parentArtifactId = "";
@Setter protected String parentVersion = "";
Expand Down Expand Up @@ -375,6 +390,9 @@ public AbstractJavaCodegen() {
cliOptions.add(CliOption.newBoolean(CONTAINER_DEFAULT_TO_NULL, "Set containers (array, set, map) default to null"));
cliOptions.add(CliOption.newBoolean(GENERATE_CONSTRUCTOR_WITH_ALL_ARGS, "whether to generate a constructor for all arguments").defaultValue(Boolean.FALSE.toString()));
cliOptions.add(CliOption.newBoolean(GENERATE_BUILDERS, "Whether to generate builders for models").defaultValue(Boolean.FALSE.toString()));
if (supportsOptionalGettersForNullableFieldsOnly()) {
cliOptions.add(CliOption.newBoolean(OPTIONAL_GETTERS_FOR_NULLABLE_FIELDS_ONLY, "Make getters of non-required fields return Optional<T> while keeping the field and setter as the raw type. Requires jackson 3 (useJackson3, which requires useSpringBoot4). Supported libraries: restclient, resttemplate, webclient (java generator) and spring (spring generator). Opt-in, disabled by default.", optionalGettersForNullableFieldsOnly));
}
cliOptions.add(CliOption.newBoolean(DISABLE_DISCRIMINATOR_JSON_IGNORE_PROPERTIES, "Ignore discriminator field type for Jackson serialization", disableDiscriminatorJsonIgnoreProperties));

cliOptions.add(CliOption.newString(CodegenConstants.PARENT_GROUP_ID, CodegenConstants.PARENT_GROUP_ID_DESC));
Expand Down Expand Up @@ -457,6 +475,7 @@ public void processOpts() {

convertPropertyToBooleanAndWriteBack(GENERATE_CONSTRUCTOR_WITH_ALL_ARGS, this::setGenerateConstructorWithAllArgs);
convertPropertyToBooleanAndWriteBack(GENERATE_BUILDERS, this::setGenerateBuilders);
convertPropertyToBooleanAndWriteBack(OPTIONAL_GETTERS_FOR_NULLABLE_FIELDS_ONLY, this::setOptionalGettersForNullableFieldsOnly);
convertPropertyToBooleanAndWriteBack(DISABLE_DISCRIMINATOR_JSON_IGNORE_PROPERTIES, this::setDisableDiscriminatorJsonIgnoreProperties);
if (StringUtils.isEmpty(System.getenv("JAVA_POST_PROCESS_FILE"))) {
LOGGER.info("Environment variable JAVA_POST_PROCESS_FILE not defined so the Java code may not be properly formatted. To define it, try 'export JAVA_POST_PROCESS_FILE=\"/usr/local/bin/clang-format -i\"' (Linux/Mac)");
Expand Down Expand Up @@ -825,6 +844,39 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs)
}
}

// When optionalGettersForNullableFieldsOnly is enabled, propagate isDiscriminator=true
// to subtype models that redefine a discriminator property from a parent/interface.
// Without this, the template would generate Optional<T> for those fields, causing a
// return-type incompatibility with the abstract getter declared by the parent interface.
if (optionalGettersForNullableFieldsOnly) {
for (ModelsMap modelsAttrs : objs.values()) {
for (ModelMap mo : modelsAttrs.getModels()) {
CodegenModel cm = mo.getModel();
if (cm.discriminator != null) {
String discPropName = cm.discriminator.getPropertyBaseName();
// propagate to all known subtype models
if (cm.discriminator.getMappedModels() != null) {
for (CodegenDiscriminator.MappedModel mapped : cm.discriminator.getMappedModels()) {
CodegenModel subModel = allModels.get(mapped.getModelName());
if (subModel != null) {
for (CodegenProperty var : subModel.vars) {
if (discPropName.equals(var.baseName)) {
var.isDiscriminator = true;
}
}
for (CodegenProperty var : subModel.allVars) {
if (discPropName.equals(var.baseName)) {
var.isDiscriminator = true;
}
}
}
}
}
}
}
}
}

if (isGenerateConstructorWithAllArgs()) {
// conditionally force the generation of all args constructor.
for (CodegenModel cm : allModels.values()) {
Expand Down Expand Up @@ -2314,6 +2366,22 @@ public ModelsMap postProcessModels(ModelsMap objs) {
writePropertyBack(LOMBOK, lombokOptions);
}

// When optionalGettersForNullableFieldsOnly is enabled, Lombok-generated getters would
// expose the raw field type instead of Optional<T>, silently bypassing the option.
// Drop the lombok.Getter/lombok.Data annotations so that explicit getters (and the
// other members normally generated by those two annotations) are rendered directly.
if (optionalGettersForNullableFieldsOnly && lombokAnnotations != null && lombokAnnotations.containsKey("Getter")) {
// Lombok skips generating a getter when one is already defined, so removing the
// lombok.Getter context-level flag makes the templates emit explicit getters
// (returning Optional<T> for non-required fields) that take precedence over
// any lombok.Data-generated accessor.
lombokAnnotations.remove("Getter");
writePropertyBack(LOMBOK, new HashMap<>(lombokAnnotations));
LOGGER.warn("{} is enabled: the lombok.Getter annotation has been disabled so that explicit " +
"Optional getters are generated. Re-enable Lombok getters by disabling this option.",
OPTIONAL_GETTERS_FOR_NULLABLE_FIELDS_ONLY);
}

return postProcessModelsEnum(objs);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ public JavaCamelServerCodegen() {
annotationLibrary = AnnotationLibrary.SWAGGER2;
}

@Override
protected boolean supportsOptionalGettersForNullableFieldsOnly() {
// java-camel uses its own java-camel-server model templates, which do not
// implement Optional getters.
return false;
}

@Override
public void processOpts() {
generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ public DocumentationProvider defaultDocumentationProvider() {
return DocumentationProvider.SOURCE;
}

@Override
protected boolean supportsOptionalGettersForNullableFieldsOnly() {
return true;
}

@Override
protected boolean useBeanValidationOnMapValueType() {
// The Java templates place container element validation on the type argument
Expand Down Expand Up @@ -426,6 +431,13 @@ public void processOpts() {
throw new IllegalArgumentException("useJackson3 is only supported for the 'native', 'apache-httpclient', 'jersey3', 'restclient', 'resttemplate', and 'webclient' libraries. " +
"The Spring libraries also require useSpringBoot4=true.");
}
if (libRestClient || libRestTemplate || libWebClient) {
convertPropertyToBooleanAndWriteBack(OPTIONAL_GETTERS_FOR_NULLABLE_FIELDS_ONLY, this::setOptionalGettersForNullableFieldsOnly);
if (optionalGettersForNullableFieldsOnly && !useJackson3) {
throw new IllegalArgumentException(OPTIONAL_GETTERS_FOR_NULLABLE_FIELDS_ONLY +
" is only supported with jackson 3 (enable useJackson3 and useSpringBoot4)");
}
}
Comment thread
jorgerod marked this conversation as resolved.

if (this.useJackson3) {
this.applyJackson3Package();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ public JavaMicroprofileServerCodegen() {
cliOptions.removeIf(opt -> TYPE_INFO_DEFAULT_IMPLS.equals(opt.getOpt()));
}

@Override
protected boolean supportsOptionalGettersForNullableFieldsOnly() {
// The microprofile library renders models through its own model.mustache,
// which does not implement Optional getters.
return false;
}

@Override
public CodegenType getTag() {
return CodegenType.SERVER;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,11 @@ public DocumentationProvider defaultDocumentationProvider() {
return isLibrary(SPRING_HTTP_INTERFACE) ? null : DocumentationProvider.SPRINGDOC;
}

@Override
protected boolean supportsOptionalGettersForNullableFieldsOnly() {
return true;
}

@Override
public List<DocumentationProvider> supportedDocumentationProvider() {
List<DocumentationProvider> supportedProviders = new ArrayList<>();
Expand Down Expand Up @@ -680,6 +685,10 @@ public void processOpts() {
if(isUseJackson3() && !isUseSpringBoot4()){
throw new IllegalArgumentException("useJackson3 is only available with Spring Boot >= 4");
}
if (optionalGettersForNullableFieldsOnly && !isUseJackson3()) {
throw new IllegalArgumentException(OPTIONAL_GETTERS_FOR_NULLABLE_FIELDS_ONLY +
" is only supported with jackson 3 (enable useJackson3 / useSpringBoot4)");
Comment thread
jorgerod marked this conversation as resolved.
}
if(this.useJackson3){
this.applyJackson3Package();
} else {
Expand Down
Loading
Loading