Skip to content

feat (JAVA NATIVE): add support for useSingleRequestParameter to java native client #21331

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/generators/java-microprofile.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|useRuntimeException|Use RuntimeException instead of Exception. Only jersey2, jersey3, okhttp-gson, vertx, microprofile support this option.| |false|
|useRxJava2|Whether to use the RxJava2 adapter with the retrofit2 library. IMPORTANT: This option has been deprecated.| |false|
|useRxJava3|Whether to use the RxJava3 adapter with the retrofit2 library. IMPORTANT: This option has been deprecated.| |false|
|useSingleRequestParameter|Setting this property to "true" will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter. ONLY jersey2, jersey3, okhttp-gson, microprofile, Spring RestClient, Spring WebClient support this option. Setting this property to "static" does the same as "true", but also makes the generated arguments class static with single parameter instantiation.| |false|
|useSingleRequestParameter|Setting this property to "true" will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter. ONLY native, jersey2, jersey3, okhttp-gson, microprofile, Spring RestClient, Spring WebClient support this option. Setting this property to "static" does the same as "true", but also makes the generated arguments class static with single parameter instantiation.| |false|
|webclientBlockingOperations|Making all WebClient operations blocking(sync). Note that if on operation 'x-webclient-blocking: false' then such operation won't be sync| |false|
|withAWSV4Signature|whether to include AWS v4 signature support (only available for okhttp-gson library)| |false|
|withXml|whether to include support for application/xml content type and include XML annotations in the model (works with libraries that provide support for JSON and XML)| |false|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/java.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|useRuntimeException|Use RuntimeException instead of Exception. Only jersey2, jersey3, okhttp-gson, vertx, microprofile support this option.| |false|
|useRxJava2|Whether to use the RxJava2 adapter with the retrofit2 library. IMPORTANT: This option has been deprecated.| |false|
|useRxJava3|Whether to use the RxJava3 adapter with the retrofit2 library. IMPORTANT: This option has been deprecated.| |false|
|useSingleRequestParameter|Setting this property to "true" will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter. ONLY jersey2, jersey3, okhttp-gson, microprofile, Spring RestClient, Spring WebClient support this option. Setting this property to "static" does the same as "true", but also makes the generated arguments class static with single parameter instantiation.| |false|
|useSingleRequestParameter|Setting this property to "true" will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter. ONLY native, jersey2, jersey3, okhttp-gson, microprofile, Spring RestClient, Spring WebClient support this option. Setting this property to "static" does the same as "true", but also makes the generated arguments class static with single parameter instantiation.| |false|
|webclientBlockingOperations|Making all WebClient operations blocking(sync). Note that if on operation 'x-webclient-blocking: false' then such operation won't be sync| |false|
|withAWSV4Signature|whether to include AWS v4 signature support (only available for okhttp-gson library)| |false|
|withXml|whether to include support for application/xml content type and include XML annotations in the model (works with libraries that provide support for JSON and XML)| |false|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ public JavaClientCodegen() {
cliOptions.add(CliOption.newString(CONFIG_KEY_FROM_CLASS_NAME, "If true, set tag as key in @RegisterRestClient. Default to false. Only `microprofile` supports this option."));
cliOptions.add(CliOption.newBoolean(CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP, CodegenConstants.USE_ONEOF_DISCRIMINATOR_LOOKUP_DESC + " Only jersey2, jersey3, native, okhttp-gson support this option."));
cliOptions.add(CliOption.newString(MICROPROFILE_REST_CLIENT_VERSION, "Version of MicroProfile Rest Client API."));
cliOptions.add(CliOption.newString(CodegenConstants.USE_SINGLE_REQUEST_PARAMETER, "Setting this property to \"true\" will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter. ONLY jersey2, jersey3, okhttp-gson, microprofile, Spring RestClient, Spring WebClient support this option. Setting this property to \"static\" does the same as \"true\", but also makes the generated arguments class static with single parameter instantiation.").defaultValue("false"));
cliOptions.add(CliOption.newString(CodegenConstants.USE_SINGLE_REQUEST_PARAMETER, "Setting this property to \"true\" will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter. ONLY native, jersey2, jersey3, okhttp-gson, microprofile, Spring RestClient, Spring WebClient support this option. Setting this property to \"static\" does the same as \"true\", but also makes the generated arguments class static with single parameter instantiation.").defaultValue("false"));
cliOptions.add(CliOption.newBoolean(WEBCLIENT_BLOCKING_OPERATIONS, "Making all WebClient operations blocking(sync). Note that if on operation 'x-webclient-blocking: false' then such operation won't be sync", this.webclientBlockingOperations));
cliOptions.add(CliOption.newBoolean(GENERATE_CLIENT_AS_BEAN, "For resttemplate, restclient and webclient, configure whether to create `ApiClient.java` and Apis clients as bean (with `@Component` annotation).", this.generateClientAsBean));
cliOptions.add(CliOption.newBoolean(SUPPORT_URL_QUERY, "Generate toUrlQueryString in POJO (default to true). Available on `native`, `apache-httpclient` libraries."));
Expand Down Expand Up @@ -800,7 +800,7 @@ public void processOpts() {
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<ModelMap> allModels) {
super.postProcessOperationsWithModels(objs, allModels);

if (this.getSingleRequestParameter() && (isLibrary(JERSEY2) || isLibrary(JERSEY3) || isLibrary(OKHTTP_GSON))) {
if (this.getSingleRequestParameter() && (isLibrary(JERSEY2) || isLibrary(JERSEY3) || isLibrary(OKHTTP_GSON) || isLibrary(NATIVE))) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wing328 This PR enables the generation of the single parameter class for the Java native client. Before this PR, enabling ‘useSingleRequestParameter’ doesn’t changed anything for the Java native client.

// loop through operations to set x-group-parameters extension to true if useSingleRequestParameter option is enabled
OperationMap operations = objs.getOperations();
if (operations != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public class {{classname}} {
/**
* {{summary}}
* {{notes}}
* @param apiRequest {@link API{{operationId}}Request}
* @param apiRequest {@link API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request}
{{#returnType}}
* @return {{#asyncNative}}CompletableFuture&lt;{{/asyncNative}}{{returnType}}{{#asyncNative}}&gt;{{/asyncNative}}
{{/returnType}}
Expand All @@ -127,7 +127,7 @@ public class {{classname}} {
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
public {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}}{{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture<Void>{{/asyncNative}}{{^asyncNative}}void{{/asyncNative}}{{/returnType}} {{operationId}}(API{{operationId}}Request apiRequest) throws ApiException {
public {{#returnType}}{{#asyncNative}}CompletableFuture<{{{returnType}}}>{{/asyncNative}}{{^asyncNative}}{{{returnType}}}{{/asyncNative}}{{/returnType}}{{^returnType}}{{#asyncNative}}CompletableFuture<Void>{{/asyncNative}}{{^asyncNative}}void{{/asyncNative}}{{/returnType}} {{operationId}}(API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request apiRequest) throws ApiException {
{{#allParams}}
{{>nullable_var_annotations}}
{{{dataType}}} {{paramName}} = apiRequest.{{paramName}}();
Expand All @@ -138,7 +138,7 @@ public class {{classname}} {
/**
* {{summary}}
* {{notes}}
* @param apiRequest {@link API{{operationId}}Request}
* @param apiRequest {@link API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request}
* @return {{#asyncNative}}CompletableFuture&lt;{{/asyncNative}}ApiResponse&lt;{{returnType}}{{^returnType}}Void{{/returnType}}&gt;{{#asyncNative}}&gt;{{/asyncNative}}
* @throws ApiException if fails to make API call
{{#isDeprecated}}
Expand All @@ -152,7 +152,7 @@ public class {{classname}} {
{{#isDeprecated}}
@Deprecated
{{/isDeprecated}}
public {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} {{operationId}}WithHttpInfo(API{{operationId}}Request apiRequest) throws ApiException {
public {{#asyncNative}}CompletableFuture<{{/asyncNative}}ApiResponse<{{{returnType}}}{{^returnType}}Void{{/returnType}}>{{#asyncNative}}>{{/asyncNative}} {{operationId}}WithHttpInfo(API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request apiRequest) throws ApiException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the PR

why do you need to wrap operationId with titlecase lambda?

Copy link
Contributor Author

@Nicklas2751 Nicklas2751 Jun 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wing328 Without it the operationId as part of the parameter class name would be starting with lowercase. For example it would be “APIdeletePetRequest” without it and “APIDeletePetRequest”. The second one is more camel case and Java typical in my opinion. :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this PR is more like "correcting" the parameter class naming, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wing328 I "corrected" the parameter class name, yes but I also enabled the generation for the java native client (see the changes in JavaClientCodegen). Before this PR this part was never generated.

{{#allParams}}
{{{dataType}}} {{paramName}} = apiRequest.{{paramName}}();
{{/allParams}}
Expand Down Expand Up @@ -574,7 +574,7 @@ public class {{classname}} {
{{#vendorExtensions.x-group-parameters}}
{{#hasParams}}

public static final class API{{operationId}}Request {
public static final class API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request {
{{#requiredParams}}
{{>nullable_var_annotations}}
private {{{dataType}}} {{paramName}}; // {{description}} (required)
Expand All @@ -584,7 +584,7 @@ public class {{classname}} {
private {{{dataType}}} {{paramName}}; // {{description}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}})
{{/optionalParams}}

private API{{operationId}}Request(Builder builder) {
private API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request(Builder builder) {
{{#requiredParams}}
this.{{paramName}} = builder.{{paramName}};
{{/requiredParams}}
Expand Down Expand Up @@ -616,8 +616,8 @@ public class {{classname}} {
return this;
}
{{/allParams}}
public API{{operationId}}Request build() {
return new API{{operationId}}Request(this);
public API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request build() {
return new API{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}Request(this);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3237,6 +3237,7 @@ public void testRestClientWithUseSingleRequestParameter_static_issue_20668() {
"String apiKey()",
"DeletePetRequest apiKey(@jakarta.annotation.Nullable String apiKey) {",
"public void deletePet(DeletePetRequest requestParameters) throws RestClientResponseException {",
"Pet getPetById(@jakarta.annotation.Nonnull Long petId) throws RestClientResponseException",
"public ResponseEntity<Void> deletePetWithHttpInfo(DeletePetRequest requestParameters) throws RestClientResponseException {",
"public ResponseSpec deletePetWithResponseSpec(DeletePetRequest requestParameters) throws RestClientResponseException {",
"public void deletePet(@jakarta.annotation.Nonnull Long petId, @jakarta.annotation.Nullable String apiKey) throws RestClientResponseException {",
Expand Down Expand Up @@ -3665,4 +3666,37 @@ public void visit(MethodCallExpr n, Void arg) {
assertTrue(defaultFields.get("testNullableEmptyReference").getVariable(0).getInitializer().get().isObjectCreationExpr());
assertTrue(defaultFields.get("testNullableComplexReference").getVariable(0).getInitializer().get().isMethodCallExpr());
}

@Test
public void testNativeClientWithUseSingleRequestParameter() {
final Path output = newTempFolder();
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("java")
.setLibrary(NATIVE)
.setAdditionalProperties(Map.of(
CodegenConstants.API_PACKAGE, "xyz.abcdef.api",
CodegenConstants.USE_SINGLE_REQUEST_PARAMETER, "true"
))
.setInputSpec("src/test/resources/3_1/java/petstore.yaml")
.setOutputDir(output.toString().replace("\\", "/"));

new DefaultGenerator().opts(configurator.toClientOptInput()).generate();

TestUtils.assertFileContains(
output.resolve("src/main/java/xyz/abcdef/api/PetApi.java"),
"public static final class APIDeletePetRequest {",
"private APIDeletePetRequest(Builder builder) {",
"public Builder petId(@javax.annotation.Nonnull Long petId) {",
"public Builder apiKey(@javax.annotation.Nullable String apiKey) {",
"Long petId()",
"String apiKey()",
"public void deletePet(APIDeletePetRequest apiRequest) throws ApiException {",
"Pet getPetById(@javax.annotation.Nonnull Long petId) throws ApiException",
"public ApiResponse<Void> deletePetWithHttpInfo(APIDeletePetRequest apiRequest) throws ApiException {",
"public void deletePet(@javax.annotation.Nonnull Long petId, @javax.annotation.Nullable String apiKey) throws ApiException {",
"public ApiResponse<Void> deletePetWithHttpInfo(@javax.annotation.Nonnull Long petId, @javax.annotation.Nullable String apiKey) throws ApiException {"
);
TestUtils.assertFileNotContains(output.resolve("src/main/java/xyz/abcdef/api/PetApi.java"),
"public record DeletePetRequest(Long petId, String apiKey){}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1501,11 +1501,11 @@ private HttpRequest.Builder testEnumParametersRequestBuilder(@javax.annotation.N
/**
* Fake endpoint to test group parameters (optional)
* Fake endpoint to test group parameters (optional)
* @param apiRequest {@link APItestGroupParametersRequest}
* @param apiRequest {@link APITestGroupParametersRequest}
* @return CompletableFuture&lt;Void&gt;
* @throws ApiException if fails to make API call
*/
public CompletableFuture<Void> testGroupParameters(APItestGroupParametersRequest apiRequest) throws ApiException {
public CompletableFuture<Void> testGroupParameters(APITestGroupParametersRequest apiRequest) throws ApiException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nicklas2751 fyi. i saw this change, which may become a breaking change for other users

for the time, we will consider this as a bug fix to correct the naming and see if there's a need to provide a fallback later (one way to fallback is to use customized templates)

@javax.annotation.Nonnull
Integer requiredStringGroup = apiRequest.requiredStringGroup();
@javax.annotation.Nonnull
Expand All @@ -1524,11 +1524,11 @@ public CompletableFuture<Void> testGroupParameters(APItestGroupParametersRequest
/**
* Fake endpoint to test group parameters (optional)
* Fake endpoint to test group parameters (optional)
* @param apiRequest {@link APItestGroupParametersRequest}
* @param apiRequest {@link APITestGroupParametersRequest}
* @return CompletableFuture&lt;ApiResponse&lt;Void&gt;&gt;
* @throws ApiException if fails to make API call
*/
public CompletableFuture<ApiResponse<Void>> testGroupParametersWithHttpInfo(APItestGroupParametersRequest apiRequest) throws ApiException {
public CompletableFuture<ApiResponse<Void>> testGroupParametersWithHttpInfo(APITestGroupParametersRequest apiRequest) throws ApiException {
Integer requiredStringGroup = apiRequest.requiredStringGroup();
Boolean requiredBooleanGroup = apiRequest.requiredBooleanGroup();
Long requiredInt64Group = apiRequest.requiredInt64Group();
Expand Down Expand Up @@ -1662,7 +1662,7 @@ private HttpRequest.Builder testGroupParametersRequestBuilder(@javax.annotation.
}


public static final class APItestGroupParametersRequest {
public static final class APITestGroupParametersRequest {
@javax.annotation.Nonnull
private Integer requiredStringGroup; // Required String in group parameters (required)
@javax.annotation.Nonnull
Expand All @@ -1676,7 +1676,7 @@ public static final class APItestGroupParametersRequest {
@javax.annotation.Nullable
private Long int64Group; // Integer in group parameters (optional)

private APItestGroupParametersRequest(Builder builder) {
private APITestGroupParametersRequest(Builder builder) {
this.requiredStringGroup = builder.requiredStringGroup;
this.requiredBooleanGroup = builder.requiredBooleanGroup;
this.requiredInt64Group = builder.requiredInt64Group;
Expand Down Expand Up @@ -1744,8 +1744,8 @@ public Builder int64Group(@javax.annotation.Nullable Long int64Group) {
this.int64Group = int64Group;
return this;
}
public APItestGroupParametersRequest build() {
return new APItestGroupParametersRequest(this);
public APITestGroupParametersRequest build() {
return new APITestGroupParametersRequest(this);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ public void testGroupParametersTest() throws ApiException {
Boolean booleanGroup = null;
Long int64Group = null;

FakeApi.APItestGroupParametersRequest request = FakeApi.APItestGroupParametersRequest.newBuilder()
FakeApi.APITestGroupParametersRequest request = FakeApi.APITestGroupParametersRequest.newBuilder()
.requiredStringGroup(requiredStringGroup)
.requiredBooleanGroup(requiredBooleanGroup)
.requiredInt64Group(requiredInt64Group)
Expand Down
Loading
Loading